【问题标题】:Flutter - getx controller not updated when data changedFlutter - 数据更改时getx控制器未更新
【发布时间】:2021-05-11 22:59:45
【问题描述】:

我正在开发一个底部导航栏有五个页面的应用程序。我使用getx。在第一页,我列出数据。我的问题是,当我从数据库手动更改数据(底部导航栏中的第一页)并传递页面时,回到第一页我看不到更改。

控制器;

class ExploreController extends GetxController {
  var isLoading = true.obs;
  var articleList = List<ExploreModel>().obs;

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

  void fetchArticles() async {
    try {
      isLoading(true);
      var articles = await ApiService.fetchArticles();
      if (articles != null) {
        //articleList.clear();
        articleList.assignAll(articles);
      }
    } finally {
      isLoading(false);
    }
    update();
  }
}

还有我的用户界面;

body: SafeArea(
        child: Column(
        children: <Widget>[
          Header(),
          Expanded(
            child: GetX<ExploreController>(builder: (exploreController) {
              if (exploreController.isLoading.value) {
                return Center(
                  child: SpinKitChasingDots(
                      color: Colors.deepPurple[600], size: 40),
                );
              }
              return ListView.separated(
                padding: EdgeInsets.all(12),
                itemCount: exploreController.articleList.length,
                separatorBuilder: (BuildContext context, int index) {

【问题讨论】:

    标签: flutter controller flutter-getx


    【解决方案1】:

    在 ui 端使用 GetxBuilder 方法以及您想要更新简单的地方调用内置函数 update();

    【讨论】:

      【解决方案2】:

      GetX 不知道/看不到数据库数据何时更改/更新。

      您需要告诉 GetX 在适当的时候重建。

      如果您将 GetX observablesGetXObx 小部件一起使用,那么您只需为您的 observable 字段分配一个新值。当obs 值更改时,将进行重建。

      如果您将 GetX 与 GetBuilder&lt;MyController&gt; 一起使用,则需要在 MyController 中调用 update() 方法来重建 GetBuilder&lt;MyController&gt; 小部件。


      以下解决方案使用 GetX 控制器(即TabX)来:

      1. 保持应用状态:

        1. 所有选项卡列表 (tabPages)
        2. 哪个选项卡处于活动状态 (selectedIndex)
      2. 公开更改活动/可见选项卡的方法 (onItemTapped())

      OnItemTapped()

      此方法在 TabX 内部,即 GetXController。

      当被调用时,它将:

      1. 设置哪个标签可见
      2. 将查看的选项卡保存到数据库 (FakeDB)
      3. 使用update() 重建任何GetBuilder 小部件
        void onItemTapped(int index) {
          selectedIndex = index;
          db.insertViewedPage(index); // simulate database update while tabs change
          update(); // ← rebuilds any GetBuilder<TabX> widget
        }
      

      完整示例

      将整个代码复制/粘贴到应用中的 dart 页面中,以查看有效的 BottomNavigationBar 页面。

      这个选项卡式/BottomNavigationBar 示例取自 https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html 但编辑为使用 GetX。

      import 'package:flutter/material.dart';
      import 'package:get/get.dart';
      
      void main() {
        runApp(MyApp());
      }
      
      class MyApp extends StatelessWidget {
        // This widget is the root of your application.
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            debugShowCheckedModeBanner: false,
            theme: ThemeData(
              primarySwatch: Colors.blue,
              visualDensity: VisualDensity.adaptivePlatformDensity,
            ),
            home: MyTabHomePage(),
          );
        }
      }
      
      class FakeDB {
        List<int> viewedPages = [0];
      
        void insertViewedPage(int page) {
          viewedPages.add(page);
        }
      }
      
      /// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
      /// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
      class TabX extends GetxController {
      
        TabX({this.db});
      
        final FakeDB db;
        int selectedIndex = 0;
        static const TextStyle optionStyle =
        TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
        List<Widget> tabPages;
      
        @override
        void onInit() {
          super.onInit();
          tabPages = <Widget>[
            ListViewTab(db),
            Text(
              'Index 1: Business',
              style: optionStyle,
            ),
            Text(
              'Index 2: School',
              style: optionStyle,
            ),
          ];
        }
      
        /// INTERESTING PART HERE ↓ ************************************
        void onItemTapped(int index) {
          selectedIndex = index;
          db.insertViewedPage(index); // simulate database update while tabs change
          update(); // ← rebuilds any GetBuilder<TabX> widget
          // ↑ update() is like setState() to anything inside a GetBuilder using *this*
          // controller, i.e. GetBuilder<TabX>
          // Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
          // by this update()
          // Use async/await above if data writes are slow & must complete before updating widget. 
          // This example does not.
        }
      }
      
      /// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
      class ListViewTab extends StatelessWidget {
        final FakeDB db;
      
        ListViewTab(this.db);
      
        @override
        Widget build(BuildContext context) {
          return ListView.builder(
            itemCount: db.viewedPages.length,
            itemBuilder: (context, index) =>
                ListTile(
                  title: Text('Page Viewed: ${db.viewedPages[index]}'),
                ),
          );
        }
      }
      
      
      class MyTabHomePage extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          Get.put(TabX(db: FakeDB()));
      
          return Scaffold(
            appBar: AppBar(
              title: const Text('BottomNavigationBar Sample'),
            ),
            body: Center(
              /// ↓ Tab Page currently visible - rebuilt by GetBuilder when 
              /// ↓ TabX.onItemTapped() called
              child: GetBuilder<TabX>(
                  builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
              ),
            ),
            /// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
            /// ↓ TabX.onItemTapped() called
            bottomNavigationBar: GetBuilder<TabX>(
              builder: (tx) => BottomNavigationBar(
                items: const <BottomNavigationBarItem>[
                  BottomNavigationBarItem(
                    icon: Icon(Icons.home),
                    label: 'Home',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.business),
                    label: 'Business',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.school),
                    label: 'School',
                  ),
                ],
                currentIndex: tx.selectedIndex,
                selectedItemColor: Colors.amber[800],
                onTap: tx.onItemTapped,
              ),
            ),
          );
        }
      }
      
      

      【讨论】:

      • 为什么要将 Get.put() 放在构建函数中?如果它在构建之外而不在每个构建上调用它不是更好吗?
      • Get 控制器生命周期与路由相关联。当路由被释放时,注册到该路由的Get 控制器也会被释放。注册为路由的字段/成员(即小部件),控制器绑定到 parent 小部件,而不是当前小部件。当当前小部件被释放(路由弹出)时,Get 控制器仍然存在(它不应该存在)。将Get.put 放置在build() 中,可以避免这种生命周期怪癖(在build() 之前无法注册当前小部件)。 1/2
      • 这里是 Eduardo,包的维护者之一,举个例子:github.com/jonataslaw/getx/issues/818#issuecomment-733652172。还有更多关于Get我创建/处置生命周期的细节:stackoverflow.com/a/65117780/23012242/2
      【解决方案3】:
      1. 创建 最后 exploreController = Get.put(ExploreController());

      2. 添加 初始化:探索控制器();

      body: SafeArea(
              child: Column(
              children: <Widget>[
                Header(),
                Expanded(
                  child: GetX<ExploreController>(builder: (exploreController) {
                                   *** here ***
                   init: ExploreController();
                    if (exploreController.isLoading.value) {
                      return Center(
                        child: SpinKitChasingDots(
                            color: Colors.deepPurple[600], size: 40),
                      );
                    }
                    return ListView.separated(
                      padding: EdgeInsets.all(12),
                      itemCount: exploreController.articleList.length,
                      separatorBuilder: (BuildContext context, int index) {
      

      【讨论】:

        【解决方案4】:

        感谢@Baker 的正确答案。但是,如果您有一个列表并且在 viewModel 中并且想要更新该列表,只需在列表更新时使用 list.refresh()

        RxList<Models> myList = <Models>[].obs;
        

        当添加或插入数据时:

        myList.add(newItem);
        myList.refresh();
        

        【讨论】:

          【解决方案5】:

          如果您“手动”更改数据库中的值,则需要一个 STREAM 来监听数据库的更改。 你不能这样做:

          var articles = await ApiService.fetchArticles();
          

          你需要做这样的事情:

          var articles = await ApiService.listenToArticlesSnapshot();
          

          您解释的方式就像您需要在导航到另一个页面并单击按钮后刷新数据,然后导航到第一页(GetBuilder)或自动从第一页(Obx)中添加数据。但是你的情况很简单,只需检索文章 SNAPSHOT,然后在控制器 onInit 中,使用 bindStream 方法订阅快照,并最终使用函数 ever() 对可观察文章列表中的任何更改做出反应。 像这样的:

          【讨论】:

            【解决方案6】:

            这里不需要 GetBuilder,因为它不适用于可观察变量。您也不需要在 fetchArticles 函数中调用 update(),因为它仅用于 GetBuilder 和不可观察变量。

            因此,您有 2 个用于更新 UI 的小部件(GetBuilder 和 Obx)都遵循同一个控制器,而您只需要 OBX。因此,Rahuls 的回答有效,或者您可以将 Obx 留在原处,摆脱 GetBuilder 并在构建方法的开头声明并初始化一个控制器。

            final exploreController = Get.put(ExploreController());
            
            

            然后在您的 OBX 小部件中使用该初始化控制器作为您的 Expanded 的子级。

            
            Obx(() => exploreController.isLoading.value
                      ? Center(
                          child:
                              SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
                        )
                      : ListView.separated(
                          padding: EdgeInsets.all(12),
                          itemCount: exploreController.articleList.length,
                          separatorBuilder: (BuildContext context, int index) {},
                        ),
                )
            

            【讨论】:

            • 我试过这个。我有我的控制器。我看不到新数据,我必须刷新页面才能看到新数据。
            • 通过添加 .obs 使您的数据可观察。
            • 除了控制器 onInit 之外,您是否正在从其他任何地方触发获取文章功能?因为至少从我所见,这将是数据更新的唯一时间是该函数被触发时。此外,如果您在未手动刷新的情况下无法在应用程序启动时看到更新的数据,请在运行应用程序之前在您的主要方法中尝试 await Get.put(ExploreController())。然后您的应用将开始显示更新的数据。
            【解决方案7】:
             GetX< ExploreController >(builder: (controller) {
                    if (controller.isLoading.value) {
                      return Center(
                        child: SpinKitChasingDots(
                            color: Colors.deepPurple[600], size: 40),);
                    }
                    return ListView.separated(
                        padding: EdgeInsets.all(12),
                        itemCount: controller.articleList.length,
                        separatorBuilder: (BuildContext context, int index) {});
                  });
            

            【讨论】:

            • 你好,我试过了,我更新了我的代码。但什么都没有改变。希望你能理解我的问题。
            • 通过添加 .obs 使您的数据可观察
            • 我有 obs。 var articleList = List().obs;
            猜你喜欢
            • 2021-07-15
            • 2021-01-22
            • 2022-01-18
            • 2021-12-17
            • 2023-01-18
            • 1970-01-01
            • 2021-07-22
            • 2021-10-08
            • 1970-01-01
            相关资源
            最近更新 更多