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
using
future: _repo.getAll()
, setState will recall the api. You need to create a future variable and then put it onfuture
.More about Fixing a common FutureBuilder and StreamBuilder problem
When using a
FutureBuilder
you should not need to usesetState
, as the actual update of the state, based on the Future’s response, will be managed by theFutureBuilder
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 newString
is added to theDatabase
, that the widget displays reactively the new data.In this case probably you want to use a
StreamBuilder
, which is very similiar to aFutureBuilder
, 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 theDatabase
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 aFutureBuilder
orStreamBuilder
, but you would listen to the changes coming from theProvider
, via aConsumer
, then when your button is pressed, theProvider
can add that piece of data to theobject
and yourUI
would see immediately the changes.I can provide you a simple example:
your provider, that manages the state:
your UI widget
As you can see, the
Provider
will fetch once the data, and then it will hold the state, and once a newString
is added, it will only submit it to the backend, but it won’t be refetching all the data again.