One thing I’ve really liked about Android development (yes, this is an article about Flutter) in the past few years is how Google has been writing their opinionated Android Architecture Components libraries. Those libraries prescribe a pattern of persisting data locally and having that data drive the UI. I like this because it gives an app a single source of truth for its data — the database. This also has a nice side effect of facilitating an app’s offline use.

There’s an awesome class that isn’t officially attached to the AAC libraries but is used in one of Google’s AAC samples. It’s called NetworkBoundResource. I think this class is great because it solved a tricky problem I had. Like many apps, I wanted one of mine to fetch data remotely, store it locally in a database, then propagate that data to the UI. Before initially fetching the new data, however, I wanted my UI to show any data that was already stored locally, while still communicating that background work to fetch new data was in progress. NetworkBoundResource provided a great API to deal with this situation by publishing data to the UI within a wrapper class called Resource<T> . This class holds arbitrary data and is accompanied by a field describing its state, i.e. whether it is loading, is successful, or had an error.

NetworkBoundResource publishes events to the UI in the following way and order:

Resource.loading(null). This is simply a signal to the UI that background work has begun; the data accompanied with this object is null — the UI will likely then only show something like a progress spinner. Resource.loading(data). This is another signal to the UI that background work is happening. The resource that the UI receives also wraps any data that was found locally. This is really useful because the UI receives data to show but understands that work is still being done in the background. For example, the UI could show a list of items but with a progress spinner appearing on top of them. Either Resource.success(data) or Resource.error(data, error). The former is a Resource wrapper around data that was fetched remotely then stored/queried locally and a state that signals everything went as planned. The latter is a wrapper around any data that was found locally and an accompanying error object describing how something went wrong. The latter object is great because it isn’t only just a wrapper for an exception — it potentially also has data for the UI to display, so not all is lost in the event of a failure.

I liked this way of publishing events to the UI so much that I wanted to incorporate its ideas into a Flutter app. I decided to try an implementation of it using data from the GitHub Jobs API. After a day of messing around with FutureBuilder<T> and failing to get the behavior I wanted, I abandoned that class and later arrived at a much simpler implementation using the provider package which I’m currently satisfied with.

The core ideas of NetworkBoundResource are encapsulated within a custom ChangeNotifier subclass, which I’ve called GitHubJobsViewModel . It initially looks like so:

class GitHubJobsViewModel extends ChangeNotifier {

Resource<List<GitHubJob>> _jobs = Resource.success([]);

Resource<List<GitHubJob>> get jobs => _jobs;



final GitHubJobsRepository repository;

GitHubJobsViewModel({@required this.repository});



/* ... */

}

I’ve appended the class name with ViewModel because to me its role resembles that of the AAC ViewModel class: to hold data that the UI can listen to for changes. In the class so far is a _jobs field exposed through a jobs getter that the UI can inspect when deciding what to show. This field is a Resource<T> wrapper for a List<GitHubJob> , which is a custom PODO class I’ve defined to model an item within the JSON that the GitHub Jobs API provides, and is initialized with empty data. The main dependency this class has is on a GitHubJobsRespository instance, which is simply a class that stitches together Future<T> s coming from either a web service or the local database, and serves as a data abstraction layer.

The real meat of the class, which so far I’ve omitted, is the method which publishes events to the UI in the way that NetworkBoundResource does. It looks like this:

class GitHubJobsViewModel extends ChangeNotifier {

Resource<List<GitHubJob>> _jobs = Resource.success([]);

Resource<List<GitHubJob>> get jobs => _jobs;



final GitHubJobsRepository repository;

GitHubJobsViewModel({@required this.repository});



Future<void> _run(Future<List<GitHubJob>> Function() runnable) async {

if (_jobs.state == ResourceState.LOADING) {

return;

}



_jobs = Resource.loading(_jobs.data);

notifyListeners();



_jobs = Resource.loading(await repository.queryAll());

notifyListeners();



_jobs = await runnable()

.then((data) => Resource.success(data))

.catchError((error) => Resource.error(_jobs.data, error));

notifyListeners();

} /* ... */ }

It uses the same steps that NetworkBoundResource does to communicate with the UI: multiple Resource.loading() events followed by either a Resource.success() or Resource.error(). I’ve made two small changes to those steps, however. The first is to guard the method with a check for whether the state of the _jobs Resource is loading or not. This will prevent the classes method getting spammed with requests for background work before ongoing work is finished. If that doesn’t suit your use case, feel free to remove it. The second is having the initial Resource.loading() event get passed any data in the _jobs object rather than just null ; I felt that this gives more options to the UI in deciding what to show.

This event propagation is all made possible by the provider package’s notifyListeners() method. Widgets that are listening to this will be rebuilt each time that method is called and will get the latest state of the _jobs field.

The last part of the class that I’ve omitted is how the private _run method gets its argument for the peculiar Future<List<GitHubJob>> Function() runnable parameter. This parameter is effectively just a reference to a method that returns a Future<List<GitHubJob>> ; having it passed as a reference facilitates deferring the start of the Future<T> ’s work. That use is found below:

class GitHubJobsViewModel extends ChangeNotifier {

Resource<List<GitHubJob>> _jobs = Resource.success([]);

Resource<List<GitHubJob>> get jobs => _jobs;



final GitHubJobsRepository repository;

GitHubJobsViewModel({@required this.repository});



Future<void> _run(Future<List<GitHubJob>> Function() runnable) async {

if (_jobs.state == ResourceState.LOADING) {

return;

}



_jobs = Resource.loading(_jobs.data);

notifyListeners();



_jobs = Resource.loading(await repository.queryAll());

notifyListeners();



_jobs = await runnable()

.then((data) => Resource.success(data))

.catchError((error) => Resource.error(_jobs.data, error));

notifyListeners();

}



void fetchPurgeInsertQuery() {

_run(() => repository.fetchPurgeInsertQuery());

}



void insertGitHubJob(GitHubJob gitHubJob) {

_run(() => repository.insert(gitHubJob));

}

}

The method entitled fetchPurgeInsertQuery does just that — it fetches data remotely, clears the database table of all data, inserts the newly fetched data into that table, then queries it (assuming everything worked out). The insertGitHubJob method more simply just inserts a row into the table and then queries that table, but they both make use of _run() 's mechanics to inform the UI of the current state of the data in a fluent way.

Based on my experience with this class, I found that it successfully emulates the way NetworkBoundResource published events to the UI. If you want to see the full implementation which uses the sqflite package and a few others, feel free to check out the GitHub repository below. Compare how the data is presented in the UI the first time opening the app versus subsequent app opens to see the event publishing in action.

https://github.com/nihk/database-driven-ui