Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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) {
like image 631
jancooth Avatar asked Feb 08 '21 10:02

jancooth


People also ask

How do you refresh the screen on GetX Flutter?

Answer if you change it to: _currentFilter. value = filter; GetX fires a method called refresh() that refreshes the view.

How do I change state in GetX Flutter?

To manage the state by GetX, we should create a class that extends GetxController . And then, define a variable that is for the state. Lastly, make a function to update the state. In the simple state management, after updating the state, you should call update() function to notify the state is changed.

Is GetX good for Flutter?

GetX has a huge ecosystem, a large community, a large number of collaborators, and will be maintained as long as the Flutter exists. GetX too is capable of running with the same code on Android, iOS, Web, Mac, Linux, Windows, and on your server.

Why can't GETX see when database data has changed?

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.

What is GETX in flutter?

GetX is not only a state management library, but instead, it is a microframework combined with route management and dependency injection. It aims to deliver top-of-the-line development experience in an extra lightweight but powerful solution for Flutter. GetX has three basic principles on which it is built:

How to rebuild getbuilder<MyController> widgets with GETX?

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:

What is GETX controller and how does it work?

This is where GetX Controller comes into play. You can always create more than one controller in your application. The GetX Controller class controls the state of the UI when you wrap an individual widget with its Observer so that it only rebuilds when there is a change in the state of that particular widget.


6 Answers

thanks to @Baker for the right answer. However, if you have a list and in viewModel and want to update that list, just use the list.refresh() when the list updated

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

when add or insert data act like this:

myList.add(newItem);
myList.refresh();
like image 156
Picker Avatar answered Oct 21 '22 07:10

Picker


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

like image 31
Baker Avatar answered Oct 21 '22 05:10

Baker


You don't need GetBuilder here, as its not meant for observable variables. Nor do you need to call update() in the fetchArticles function as that's only for use with GetBuilder and non observable variables.

So you had 2 widgets meant to update UI (GetBuilder and Obx) both following the same controller and all you need is just the OBX. So Rahuls answer works, or you can leave the Obx in place, get rid of of the GetBuilder and declare and initialize a controller in the beginning of your build method.

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

Then use that initialized controller in your OBX widget as the child of your 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) {},
            ),
    )
like image 42
Loren.A Avatar answered Oct 21 '22 06:10

Loren.A


 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) {});
      });
like image 37
Rahul Singh Avatar answered Oct 21 '22 07:10

Rahul Singh


If you change the value in the database 'manually', you need a STREAM to listen to the change on the database. You can't do:

var articles = await ApiService.fetchArticles();

You need to do something like this:

var articles = await ApiService.listenToArticlesSnapshot();

The way you explained is like if you need the data to refresh after navigating to another page and clicking on a button, then navigating to first page (GetBuilder) OR automatically adds data from the within the first page (Obx). But your case is simple, just retrieve the articles SNAPSHOT, then in the controller onInit, subscribe to the snapshot with the bindStream method, and eventually use the function ever() to react to any change in the observable articleList. Something like this:

like image 29
kamazoun Avatar answered Oct 21 '22 07:10

kamazoun


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

  2. Add init: 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) {
like image 29
Koffi Innocent Konan Avatar answered Oct 21 '22 07:10

Koffi Innocent Konan