Search code examples
fluttercontrollerflutter-getx

Flutter - getx controller not updated when data changed


I am developing an app that has a bottomnavitaionbar with five pages. I use getx. In first page, i am listing data. My problem is that, when i changed data(first page in bottomnavigationbar) manually from database and thn i pass over pages, came back to first page i could not see changes.

Controller;

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

and my UI;

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

Solution

  • GetX doesn't know / can't see when database data has changed / been updated.

    You need to tell GetX to rebuild when appropriate.

    If you use GetX observables with GetX or Obx widgets, then you just assign a new value to your observable field. Rebuilds will happen when the obs value changes.

    If you use GetX with GetBuilder<MyController>, then you need to call update() method inside MyController, to rebuild GetBuilder<MyController> widgets.


    The solution below uses a GetX Controller (i.e. TabX) to:

    1. hold application state:

      1. list of all tabs (tabPages)
      2. which Tab is active (selectedIndex)
    2. expose a method to change the active/visible tab (onItemTapped())

    OnItemTapped()

    This method is inside TabX, the GetXController.

    When called, it will:

    1. set which tab is visible
    2. save the viewed tab to the database (FakeDB)
    3. rebuild any GetBuilder widgets using update()
      void onItemTapped(int index) {
        selectedIndex = index;
        db.insertViewedPage(index); // simulate database update while tabs change
        update(); // ← rebuilds any GetBuilder<TabX> widget
      }
    

    Complete Example

    Copy/paste this entire code into a dart page in your app to see a working BottomNavigationBar page.

    This tabbed / BottomNavigationBar example is taken from https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html but edited to use 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,
            ),
          ),
        );
      }
    }