【问题标题】:Observe List with GetX outside of widget在小部件之外使用 GetX 观察列表
【发布时间】:2021-04-07 21:04:44
【问题描述】:

我已经隔离了一些繁重的计算,然后在接收到结果的列表时运行一个 for 循环以将它们添加到带有 items var items = [].obs; 的可观察列表中;

问题是我正在尝试从启动控制器观察项目列表,一旦列表 != [] 我将导航到另一个屏幕,所以在 onInit() 我有这个代码:

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');
    });
  }
}

尽管 itemsController.items 已填充(在 for 循环之后我打印 itemsController.items 并且它不为空),但在添加项目时,启动控制器上的工作人员不会触发。

我在这里做错了什么?这是使用 Getx 观察小部件外部变量的正确方法吗? 有人可以帮我解决这个问题吗?

编辑:在项目控制器中,我以这种方式添加项目

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

【问题讨论】:

  • 也许你可以发布 ItemsController 的代码,这样我们就可以仔细检查 newItems 是如何添加到可观察项目中的。
  • @Baker 好的,我已经编辑了问题。
  • @Baker 无论如何,我认为问题不在于项目控制器。我在隔离终止后调试代码并添加了项目,但不知道如何访问它们。
  • 也许这个 sn-p(不是我的)有助于将数据从隔离区传输回主线程:gist.github.com/jebright/a7086adc305615aa3a655c6d8bd90264
  • 这是我已经实现的 :) 当我传输数据时,我运行一个 for 循环来填充项目列表,它就像一个魅力。这就是为什么我认为问题应该出在我观察启动控制器中的项目的方式上。

标签: flutter dart flutter-getx


【解决方案1】:

这是两个控制器,一个 ever 工作人员监听另一个控制器的事件,该控制器的事件来自 Isolate 中生成的数据。

相对于任何其他异步数据源,我不知道在 Isolate 中生成数据有什么特别之处,但我对 Isolate 并不太熟悉。

控制器

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();
}

页面/w 隔离

这里是您正在使用的Isolate snippet 的编辑。 我已经将 ItemsX 控制器实例化为一个字段,并在 onInit 中实例化了 SplashX。 (应该不需要使用 Stateful Widgets,因为您可以将所有状态放入 Controller,但我不想重写 Isolate 示例)。

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
  }

更改为 _handleMessage 方法:

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

    ix.add(data); // update observable

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

最后是调试输出结果显示ever worker 处理可观察事件 (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!

非隔离页面中的控制器

使用上述相同控制器的示例,没有 Stateful Widget 页面和所有 Isolate 的噪音。

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...'),
              )
            )
          ],
        ),
      ),
    );
  }
}

【讨论】:

  • 感谢您的回答,但它对我不起作用。在带有 statefull 小部件的示例中,它可以解决问题,因为您可以 setState 接收数据。如果我这样做它会起作用,但我需要按照我发布的方式实现它,工作人员在启动控制器上监听,这有问题。为什么我这么肯定?因为在隔离完成计算后,我在飞溅上运行 Future.delayed 30 秒,并在检查项目时运行。所以问题一定出在我使用工人的方式上。
  • 现在,关于为什么我需要运行 Isolate 的问题?我有一个运行一些繁重计算的函数,当我尝试呈现某种类型的指标时,它直到函数完成才会出现。我不是异步的,但尽管如此,就像我所说的异步并不意味着并发,所以指示器仅在进行计算时出现,这在很多层面上都没用。
  • Re:第 1 条评论,添加了另一个答案,没有使用 StatefulWidget,即没有 setState(在上面的示例中不会影响可观察/曾经工作的项目,仅重建可视页面)。控制器不受小部件重建的影响,它们的生命周期是独立的并由 GetX 处理。
【解决方案2】:

继续使用 Isolate example,但不使用 StatefulWidget,即不使用 setState。

SplashX 中的ever worker 将接收从 Isolate 生成的项目。 Stateless Widget 页面将显示 Isolate 发出的最新项目。

SplashController + everworker

class SplashX extends GetxController {
  ItemsX itemsX;

  SplashX({this.itemsX});

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

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

项目控制器

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();
}

隔离控制器

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;
    }
  }
}

无状态页面

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),
        ),
      ),
    );
  }
}

【讨论】:

  • 好的,这可能需要一段时间,因为我正在尝试以我需要的方式实现它,但您的示例仍然有效,所以我会接受答案。我需要的流程是 1.Home -> 我调用 SplashScreen 的按钮 -> 启动画面控制器调用一个运行 Isolate onInit 的函数,然后启动 worker 来监听变化。隔离完成后,它会从 onMessageHandle 添加项目,然后在停止时我需要导航到另一个屏幕。如果您愿意,可以发布另一个解决方案。
  • 给您的另一个想法:在 runApp 之前使用 Bindings 类来启动您的 Isolate 进程,并使用 worker 实例化一个控制器以侦听最终的 Isolate 数据。这是使用绑定的基础知识,包括阻塞和非阻塞stackoverflow.com/a/64841379/2301224
猜你喜欢
  • 2021-10-05
  • 2021-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 2016-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多