i6x86
i6x86

Reputation: 1627

Observe List with GetX outside of widget

I have isolate that makes some heavy calculations then on receive the list with the result run a for loop to add them to observable list with items var items = [].obs;

The thing is I'm trying to observe the items list from a splash controller and once the list != [] I'll navigate to another screen, so in onInit() I have this code:

class SplashController extends GetxController {
  @override
  void onInit() {
    final ItemsController _itemsController = Get.put(ItemsController());

    // TODO: implement onInit
    super.onInit();
    ever(_itemsController.items, (newItems) {
      print('new items here $newItems');
    });
  }
}

Despite the itemsController.items is populated (after the for loop I print the itemsController.items and it's not empty) the worker on the splash controller doesn't trigger when the items are added.

What am I doing wrong here? Is this the correct way to observe variable outside of widget using Getx? Can anyone help me with this, please?

Edit: In the items controller I’m adding the items this way

add(item) => items.add(item)

Upvotes: 3

Views: 21877

Answers (2)

Baker
Baker

Reputation: 27990

Continuing with the Isolate example, but without using a StatefulWidget i.e. no setState usage.

The ever worker in SplashX will receive items generated from the Isolate. The Stateless Widget page will display the latest item emitted from the Isolate.

SplashController + ever worker

class SplashX extends GetxController {
  ItemsX itemsX;

  SplashX({this.itemsX});

  @override
  void onInit() {
    super.onInit();

    ever(itemsX.items, (items) => print('Ever items: $items'));
  }
}

Items Controller

class ItemsX extends GetxController {
  RxList<String> items = RxList<String>();
  bool running = false;

  void add(String item) {
    items.add(item);
  }

  void updateStatus(bool isRunning) {
    running = isRunning;
    update();
  }

  void reset() {
    items.clear();
  }

  /// Only relevant for UnusedControllerPage
  List<Widget> get texts => items.map((item) => Text('$item')).toList();
}

Isolate Controller

class IsolateX extends GetxController {
  IsolateX({this.itemsX});

  ItemsX itemsX;
  Isolate _isolate;
  static int _counter = 0;
  ReceivePort _receivePort;
  bool running = false;

  static void _checkTimer(SendPort sendPort) async {
    Timer.periodic(Duration(seconds: 1), (Timer t) {
      _counter++;
      String msg = 'notification ' + _counter.toString();
      print('SEND: ' + msg);
      sendPort.send(msg);
    });
  }

  void _handleMessage(dynamic data) {
    itemsX.add(data); // update observable
  }

  void updateStatus(bool isRunning) {
    running = isRunning;
    update();
  }

  void start() async {
    itemsX.reset();
    updateStatus(true);
    _receivePort = ReceivePort();
    _isolate = await Isolate.spawn(_checkTimer, _receivePort.sendPort);
    _receivePort.listen(_handleMessage, onDone:() {
      print("done!");
    });
  }

  void stop() {
    if (_isolate != null) {
      updateStatus(false);
      _receivePort.close();
      _isolate.kill(priority: Isolate.immediate);
      _isolate = null;
    }
  }
}

Stateless Page

class MyHomePageStateless extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ItemsX ix = Get.put(ItemsX()); // Instantiate ItemsController
    IsolateX isox = Get.put(IsolateX(itemsX: ix));
    SplashX sx = Get.put(SplashX(itemsX: ix));

    return Scaffold(
      appBar: AppBar(
        title: Text('Isolate Stateless'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            GetX<ItemsX>(
              builder: (ix) => Text(ix.items.isNotEmpty ? ix.items.last : ''),
            ),
          ],
        ),
      ),
      floatingActionButton: GetBuilder<IsolateX>(
        builder: (_ix) => FloatingActionButton(
          onPressed: _ix.running ? isox.stop : isox.start,
          tooltip: _ix.running ? 'Timer stop' : 'Timer start',
          child: _ix.running ? Icon(Icons.stop) : Icon(Icons.play_arrow),
        ),
      ),
    );
  }
}

Upvotes: 6

Baker
Baker

Reputation: 27990

Here's two controllers, with one ever worker listening for events of another controller, where that controller's events are coming from data generated in an Isolate.

I'm not aware of anything special about generating data in an Isolate as opposed to any other async data source, but I'm not overly familiar with Isolates.

Controllers

class SplashX extends GetxController {
  ItemsX itemsX;

  SplashX({this.itemsX});

  @override
  void onInit() {
    super.onInit();

    ever(itemsX.items, (items) => print('Received items: $items'));
  }
}

class ItemsX extends GetxController {
  RxList<String> items = RxList<String>();

  void add(String item) {
    items.add(item);
  }

  /// Only relevant for SimplePage at bottom
  List<Widget> get texts => items.map((item) => Text('$item')).toList();
}

Page /w Isolate

And here's the edits to the Isolate snippet which you're using. I've instantiated ItemsX controller as a field and SplashX in onInit. (There shouldn't be a need to use Stateful Widgets since you can put all state into a Controller, but I didn't want to rewrite the Isolate example).

class _MyHomePageState extends State<MyHomePage> {
  Isolate _isolate;
  bool _running = false;
  static int _counter = 0;
  String notification = "";
  ReceivePort _receivePort;
  ItemsX ix = Get.put(ItemsX()); // Instantiate ItemsController

  @override
  void initState() {
    super.initState();
    SplashX sx = Get.put(SplashX(itemsX: ix));
    // ↑ Instantiate SplashCont with ever worker
  }

Change to the _handleMessage method:

  void _handleMessage(dynamic data) {
    //print('RECEIVED: ' + data);

    ix.add(data); // update observable

    setState(() {
      notification = data;
    });
  }

And finally the debug output results showing ever worker handling observable events (Received items...) :

[GETX] "ItemsX" has been initialized
[GETX] "SplashX" has been initialized
I/flutter (19012): SEND: notification 1
I/flutter (19012): Received items: [notification 1]
I/flutter (19012): SEND: notification 2
I/flutter (19012): Received items: [notification 1, notification 2]
I/flutter (19012): SEND: notification 3
I/flutter (19012): Received items: [notification 1, notification 2, notification 3]
I/flutter (19012): done!

Controllers in Non-Isolate Page

Example of using the same controllers above, without the noise of a Stateful Widget page and all the Isolate stuff.

class SplashX extends GetxController {
  ItemsX itemsX;

  SplashX({this.itemsX});

  @override
  void onInit() {
    super.onInit();

    ever(itemsX.items, (items) => print('Received items: $items'));
  }
}

class ItemsX extends GetxController {
  RxList<String> items = RxList<String>();

  void add(String item) {
    items.add(item);
  }

  /// Only relevant for SimplePage
  List<Widget> get texts => items.map((item) => Text('$item')).toList();
}

class SimplePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ItemsX ix = Get.put(ItemsX());
    SplashX sx = Get.put(SplashX(itemsX: ix));

    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              flex: 10,
              child: Obx(
                    () => ListView(
                  children: ix.texts,
                ),
              ),
            ),
            Expanded(
              flex: 1,
              child: RaisedButton(
                child: Text('Add'),
                onPressed: () => ix.add('more...'),
              )
            )
          ],
        ),
      ),
    );
  }
}

Upvotes: 1

Related Questions