skip to Main Content

I’m trying to get my head around how I should manage reading data from a database in Flutter (or any API/backend).

Without a database and just using state that’s local to the widget I’d expect to write something similar to the following. I am printing out a list of strings in a ListView and adding a new one when a FloatingActionButton is pressed.

class _ListScreenState extends State<ListScreen> {
  var _myStrings = ["string1", "string2"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: _myStrings.length,
        itemBuilder: (BuildContext context, int index) {
          return Text(_myStrings[index]);
        }
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => {
            setState(() {
              _myStrings.add("another string");
            });
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

However, when reading from a database, I’ve extracted the database logic behind a repository class and so I’m using a FutureBuilder.

class _ListScreenState extends State<ListScreen> {
  final _repo = FooRepository();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
          future: _repo.getAll(),
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
            if (snapshot.data == null) {
              return const Center(child: Text("Loading..."));
            }
            return ListView<String, String>(
                elements: snapshot.data,
                itemBuilder: (context, element) {
                  return Text(element);
                }
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => {
          _repo.add("newstring");
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

This seems to go against the idea of the flutter widget being reactive to changes in its local state. Am I doing something wrong here? I don’t know whether calling setState() will cause the FutureBuilder to redraw but it also seems wasteful to re-get all the records from the database when one is added. Should/can I hold a copy of the strings locally to the widget and add to both the database but also the local list?

2

Answers


  1. using future: _repo.getAll(), setState will recall the api. You need to create a future variable and then put it on future.

    class _ListScreenState extends State<ListScreen> {
      final _repo = FooRepository();
      late Future future = _repo.getAll();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: FutureBuilder(
              future: future,
    

    More about Fixing a common FutureBuilder and StreamBuilder problem

    Login or Signup to reply.
  2. When using a FutureBuilder you should not need to use setState, as the actual update of the state, based on the Future’s response, will be managed by the FutureBuilder itself.

    The problem here is that FutureBuilder is used when you want to retrieve some data once, but from your example, you would like that once you press your button and a new String is added to the Database, that the widget displays reactively the new data.
    In this case probably you want to use a StreamBuilder, which is very similiar to a FutureBuilder, but it’s used when you want to display some data that can change over time.

    With your example, using a FutureBuilder, when you add some data to the Database the only way to get the new data would be to refetch it via the _repo.getAll() method.
    This can work if it’s what you want, you simply can force a rebuild once the button is pressed, and the future will run again.

    To answer your final question, if you want you can manage your state locally, in this case, I would suggest to refactor your code, and add some state management logic, for example a simple Provider.
    There you can initialize your data by calling once _repo.getAll(), and in your Widget, you would not use anymore a FutureBuilder or StreamBuilder, but you would listen to the changes coming from the Provider, via a Consumer, then when your button is pressed, the Provider can add that piece of data to the object and your UI would see immediately the changes.
    I can provide you a simple example:

    your provider, that manages the state:

    class YourDataProvider extends ChangeNotifier {
          bool isLoading = true;
          List<String> myData = [];
        
          Future<void> getAll() async {
            isLoading = true;
            notifyListeners();
        
            myData = await yourAsyncCall();
        
            isLoading = false;
            notifyListeners();
          }
        
          Future<void> add(String value) async {
            isLoading = true;
            notifyListeners();
        
            await yourAddAsyncCall(value);
        
            myData.add(value);
        
            notifyListeners();
          }
        }
    

    your UI widget

    .....
      @override
      void initState() {
        context.read<YourDataProvider>.getAll();
        super.initState();
      }
    
    class _ListScreenState extends State<ListScreen> {
    
      @override
      Widget build(BuildContext context) {
        YourDataProvider provider = context.watch<YourDataProvider>;
    
        return Scaffold(
          body: provider.isLoading
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
            itemCount: provider.myData.length,
            itemBuilder: (context, index) => Text(provider.myData[index]),
          )
        ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => provider.add("newstring"),
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

    As you can see, the Provider will fetch once the data, and then it will hold the state, and once a new String is added, it will only submit it to the backend, but it won’t be refetching all the data again.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search