What pattern do you use when a widget needs to refresh data from the same async data source?

For example, when the widget first displays I pull the data and display it. Then a user edits or deletes something and I need to grab the data again and refresh the screen.

If all I needed to do was the initial data pull, then I know I can use FutureBuilder. But since I need to pull the data again and again, I don’t think FutureBuilder will work.

Can I just use setState, initState and some sort of updateState method?

Is StreamBuilder the correct way to go?

A StreamBuilder or ValueListenableBuilder is a possibility. I recommend looking into State Management solutions like my package watch_it with get_it to get an easy approach.

Gotta say it again. Riverpod.

3 Likes
  1. Don’t overcomplicate with state managements (especially Riverpod. That sucks, a lot).

  2. There are a lot of ways to make something trigger a rebuild:

a) ValueNotifier: It’s a variable with a .value of type T. You use ValueListenableBuilder and set that notifier. Whenever the .value changes, your ValueListenableBuilder.build method triggers.

b) ChangeNotifier: it’s like the above, but it is not automatic. It will notify when you call the notifyListeners() method. That means that you can put a lot of values inside a class that inherits ChangeNotifier, along with methods (so, let’s say: a bool for userIsAuthenticated, a User for authenticatedUser, a method signIn() to sign in the user, etc. You listen to those changes using ListenableBuilder (whenever your ChangeNotifier.notifyListeners() is called, the ListenableBuilder.build is triggered.

c) setState is very underestimated. It has a huge advantage that your widget will hold a copy of the state (and that will not be lost when you hot reload your app!). You could use any other method described here to change a local variable using setState (for example: initState() register a Stream listener, dispose() unregister it, your listener call setState with the value received, copying into a local variable). Hot reload state is maintained that way.

d) Stream and StreamBuilder: that is VERY powerful. Works with both streams (StreamController), that is kinda like a socket: you emit values into it, your StreamBuilder rebuilds. Also accepts generator functions (a function where each yield is a new value pushed to the stream), basically, it makes a method be the source of the stream values (for example, when you are doing authentication, you can yield ImBusingWaitingForGoogle;, then auth with google, then yield thatWorkedImSendingInfoToTheBackend;, do some stuff on your back end and then finally yield authenticationSuccessful;. Each yield will be added to the stream (and also all exceptions thrown). A good way to make a method that do a lot of steps maintain a state.

b) is basically a copy of WPF ChangeNotifier, the thing that makes WPF data binding and had originated the MVVM architecture.

All those options are available in Dart/Flutter, so no external dependencies. Also, you don’t need to learn anything to use them.

3 Likes

I used StreamBuilder with a StreamController, it’s easy to use and works great.

It’s how I started after I wrote get_it to access the objects that held the streams.