Flutter - 数据更改时 getx 控制器未更新

问题描述 投票:0回答:9

我正在开发一个应用程序,其底部导航栏有五个页面。我用的是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
9个回答
40
投票

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

list.refresh()

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

当添加或插入数据时,如下所示:

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

20
投票

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

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

如果您将 GetX

observables
GetX
Obx
小部件一起使用,那么您只需为
observable
字段分配一个新值即可。当
obs
值发生变化时,就会发生重建。

如果您将 GetX 与

GetBuilder<MyController>
一起使用,那么您需要调用
update()
内部的
MyController
方法,以重建
GetBuilder<MyController>
小部件。


下面的解决方案使用 GetX 控制器(即

TabX
)来:

  1. 保持应用状态:

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

    onItemTapped()
    )

OnItemTapped()

此方法位于 GetXController

TabX
内部。

当被调用时,它会:

  1. 设置哪个选项卡可见
  2. 将查看的选项卡保存到数据库(
    FakeDB
    )
  3. 使用
    update()
  4. 重建任何 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,
        ),
      ),
    );
  }
}


5
投票

这里不需要 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) {},
            ),
    )

2
投票
 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) {});
      });

2
投票

在 ui 端使用

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


1
投票

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

var articles = await ApiService.fetchArticles();

你需要做这样的事情:

var articles = await ApiService.listenToArticlesSnapshot();

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


1
投票
  1. 创建 最终 exploreController = Get.put(ExploreController());

  2. 添加 初始化:ExploreController();

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) {

1
投票

我能用的最简单的方法。 在控制器中创建一个 obs (var indexClick = 1.obs;) 在每个 Tile 测试中 selected==index...; 单击每个项目时,依次更改索引单击

  return Obx(() {
  return Drawer(
    child: ListView(
      padding: EdgeInsets.zero,
      children: [
        ListTile(
          leading: const Icon(Icons.dns),
          title: const Text('Menu1'),
          selected: controller.indexClick.value==1?true:false,
          onTap: () {
            controller.indexClick.value=1;
            Navigator.pop(context);
          },
        ),
        ListTile(
          leading: const Icon(Icons.search),
          title: const Text('Menu2'),
          selected: controller.indexClick.value==2?true:false,
          onTap: () {
            controller.indexClick.value=2;
            Navigator.pop(context);
          },
        ),

0
投票

您可以像这样创建一个基本控制器:

class BaseController extends GetxController with ReadyMixin {

  @override
  void onInit() async {
    Get.log('.......... ON INIT ..........');
    super.onInit();
  }
  
  @override
  FutureOr<void> initController() {
  }

    @override
  void onReady() async {
    Get.log('.......... ON READY ..........');
    super.onReady();
  }

}

mixin ReadyMixin on GetxController {
  final isControllerReady = false.obs;

  FutureOr<void> initController();

  FutureOr<void> controllerReady() {}

  @override
  void onInit() async {
    await initController();
    super.onInit();
  }

  @override
  void onReady() async {
    isControllerReady.value = true;
    await controllerReady();
    super.onReady();
  }
}


class ProductDetailsController extends BaseController  {
  final product = ProductModel().obs;

  @override
  void onInit() async {
    product.value = (Get.arguments['product'] as ProductModel);
    Get.log('.......... ON INIT DETAILS ..........');
    super.onInit();
  }

  @override
  void onReady() async {
    Get.log('.......... ON READY DETAILS ..........');
    super.onReady();
  }

  @override
  FutureOr<void> initController() {
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.