States Rebuilder – ZERO Boilerplate Flutter State Management

It seems as if good state management comes with some hidden fees. Those can be in the form of boilerplate, extensive code generation or unmaintainability. ChangeNotifier is just too simple and there's a substantial amount of boilerplate when you move away from "counter apps". MobX has a lot of specifics and code gen. BLoC uses Streams which not everybody is comfortable with and there's also quite a bit of boilerplate.

On the other hand, states_rebuilder is the simplest yet most feature-packed state management package out there, and I don't say it lightly. As a bonus, there is LITERALLY no boilerplate .

The project we will build

You're going to learn states_rebuilder by building a real(ish) weather forecast app. This is a tradition here, on Reso Coder, so you can compare the app we build today with it's BLoC and MobX counterparts. Here's how it will look like:

The foundations of the project together with the basic UI are already laid down in the starter project. It contains a Weather model class holding a city name and a temperature. There's also a fake WeatherRepository which simulates fetching the forecast over a network by simply generating a random temperature.

Adding a dependency

All you need for state management with states_rebuilder comes bundled in a single package.


    sdk: flutter
  states_rebuilder: ^1.10.0

Creating a WeatherStore

The official docs are kind of vague in how you should name the class holding the state management logic. That's probably because it's a pure Dart class without any dependencies on states_rebuilder whatsoever. Let's borrow the naming convention from MobX and create a WeatherStore.

Because we want to show a weather forecast, it will have a field holding the Weather model class. The UI will call getWeather() to kick off the fake network request.


class WeatherStore {
  final WeatherRepository _weatherRepository;


  Weather _weather;
  Weather get weather => _weather;

  void getWeather(String cityName) async {
    _weather = await _weatherRepository.fetchWeather(cityName);

How can something this simple be useful? I mean, how is the UI going to display a CircularProgressIndicator when there's no loading field? How will the UI even know when to rebuild since there doesn't seem to be any call to an equivalent notifyListeners()?

This will all be handled by states_rebuilder's ReactiveModel wrapper class. This is absolutely amazing, because as you can see, there is not a single line of code which isn't just pure Dart. Testing this class will be a total bliss.

Injecting the store

The states_rebuilder package injects classes through the service locator pattern, much like get_it. This is on the contrary with injection libraries such as provider which utilize Flutter's widget tree for storing and looking up dependencies by using an InheritedWidget. Don't make this fool you though! The Injector class is still a StatefulWidget and the injected dependencies still get unregistered in dispose().

While you can technically inject any class with the Injector, it supports only singletons and is really geared towards holding classes which should be wrapped in a ReactiveModel at some point. Use a different DI solution for other classes.

Let's make the WeatherStore available to the WeatherSearchPage. We're going to instantiate the simplest possible Injector widget. It has many useful callbacks though, so check out the docs.


class MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Weather App',
      home: Injector(
        inject: [
          Inject<WeatherStore>(() => WeatherStore(FakeWeatherRepository())),
        builder: (context) => WeatherSearchPage(),

Building the UI

The starter project already contains much of the WeatherSearchPage UI already set up. We just need to make it interactive by reacting to the state of the WeatherStore. That's what the StateBuilder widget is for!


class WeatherSearchPage extends StatelessWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: StateBuilder<WeatherStore>(
          // getAsReactive returns ReactiveModel<WeatherStore>
          models: [Injector.getAsReactive<WeatherStore>()],
          builder: (context, reactiveModel) {
            if (reactiveModel.isWaiting) {
              return buildLoading();
            } else if (reactiveModel.hasData) {
              return buildColumnWithData(;
            // isIdle or hasError
            return buildInitialInput();
ReactiveModel is what allows the WeatherStore class not to be dependent on any external classes. It observes and notifies us about everything going on in the store. As you can see, that includes even the status any Future within the store.

The UI has to always communicate with the ReactiveModel wrapper and not with the "dumb" store directly.

The code above will work perfectly fine. However, if you don't like if statements and you'd rather use an exhaustive switch, there's also a whenConnectionState method.


builder: (context, reactiveModel) {
  return reactiveModel.whenConnectionState(
    onIdle: () => buildInitialInput(),
    onWaiting: () => buildLoading(),
    onData: (store) => buildColumnWithData(,
    onError: (_) => buildInitialInput(),

Initializing state change

Whenever a new city name is submitted, the getWeather() method on the WeatherStore should be called. All the way down inside weather_search_page.dart, the starter project has a submitCityName() method. In it, let's again use the Injector to get a ReactiveModel<WeatherStore> and set the state.


void submitCityName(BuildContext context, String cityName) {
  final reactiveModel = Injector.getAsReactive<WeatherStore>();
    (store) => store.getWeather(cityName),
It's again extremely important to not call methods on the "dumb" store directly. ReactiveModel is what makes state management possible.

Showing a SnackBar as a side effect

We're building widgets in reaction to state changes, we're calling getWeather() properly but there's still one thing missing for the app to be complete. We need to notify the user whan there's some error by showing a SnackBar. Showing dialogs and snack bars are examples of side effects, because rebuilding the UI is not necessary.

There are multiple ways ways to perform side effects with states_rebuilder. Since this tutorial isn't about showing you every single crevice in it's public interface, I'll show you the one most suited and straightforward for reacting to errors. That's the onError callback inside setState.

There's only one error ( NetworkError ) we're aware of that can be thrown from the WeatherRepository and we'll want to rethrow any unexpected ones. Learn why in a tutorial dedicated to error handling.


void submitCityName(BuildContext context, String cityName) {
  final reactiveModel = Injector.getAsReactive<WeatherStore>();
    (store) => store.getWeather(cityName),
    // Handle errors from the call site and without any setup on our side
    onError: (context, error) {
      if (error is NetworkError) {
            content: Text("Couldn't fetch weather. Is the device online?"),
      } else {
        // Rethrow an unexpected error
        throw error;

This is all the code it takes to manage state with states_rebuilder. Boilerplate is just not present and the fact that we don't have to extend WeatherStore with some package-specific class or that we don't have to run code generation is also amazing.

As of now, this package is quite unknown, so go ahead and give it a like on pub and a star on GitHub to show its author some support.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter freelancer and most importantly developer educator, he doesn't have a lot of free time 😅 Yet he still manages to squeeze in tough workouts 💪

You may also like

Snackbar, Toast & Dialog in Flutter (Flash Package)

Search Bar in Flutter – Logic & Material UI

  • In your written tutorial, you suggest using other DI solutions for classes that are not supposed to be wrapped with ReactionModel because the injector only uses singletons.

    I would like to draw your attention here the following points:

    1 – Injector registers classes in the initState and unregisters them in the dispose state.
    2 – You can use nested Injectors and any class should be injected deep in the widget tree when first need.
    3 – Classes are registered lazily, they are not instantiated until first needed.
    4 – You can register with concrete types or abstract classes.

    Taking this into consideration :
    – The equivalent of registerSingleton in get_it is to inject the class at the topmost widget so it will be available to all the app. (you can set isLazy to false).

    – The equivalent of registerLazySingleton is the default behavior. Even more appropriate, you can inject the class deep in the widget tree where they first need. You benefit from the fact that when you go back in the widget tree, the Injector is disposed, and the model is unregistred because is no longer used by the app . This is contrary to what happens with get_it, if you go up in the widget tree, the model remains saved even not necessary. Imagine that a user navigates through all the screens available in the application, he will instantiate the lazy singletons which will remain alive even if the user returns to the home screen. With Injector, you can manage to destroy them and take advantage of this opportunity to dispose resources.

    – The equivalent of registerFactory is the same as above, that is, injecting the class when first needed deep in the widget tree. Whenever you go down the widget tree you instantiate the class (in intState) and if you go up you destroy it (in dispose), and so forth each time a new instance is created. As a bonus, you can get the registered instance using Injector.get method, wheres with get_it you can not get a the same instance registered with registerFactory using the method.

    – With Injector, you can inject futures and use whenConnectionState to display useful information to the user and finally you can get the registered instance using Injector.get method anywhere in the app. This is useful for instantiating plug-ins such as SharedPreferences. So you do not have to make the main function async and wait before calling runApp and use WidgetsFlutterBinding.ensureInitialized().
    For example, you can show a splash screen informing the user that something is instantiating and display a helping error message if a plug-in fails to initialize.

    • Hi Mellati, first of let me also say thank you for this elaborate and well made package, simply outstanding!

      I’m still going through both the depth and breadth of all the info you have on the package documentation itself and all the examples in it.

      Adding to that, the collection of articles you have listed here is also amazing: thremendous.

      I also noticed that it is included in the Flutter Architecture Examples now:

      Just wow, many better known packages don’t even come close to this. I’m still trying to absorb all the documentation and info. Even that older article you have there with Scoped Model/Provider vs BloC vs States Rebuild is still very interesting and relevant in its own way. I have struggled with some of the issues that example demonstrates. I’m still waiting for someone to publish a solution repeating the things you do with States Rebuilder (including the animation) with Provider or BLoC or anything in that example. Seemed to me that all the trials in the comments to the article failed so far.

      I know Matt/Resocoder had some reservations after trying States Rebuilder in a project (he mentioned it on Twitter). I just don’t know the full details of it. If Matt would care to explain what it was, here or even in a separate follow up post to this article and video, it would be useful to learn more about it and interesting to hear if you have a solution to whatever it was.

      In any case, I’m going to continue to go through all the documents and articles and then take States Rebuilder for a test ride.

      Another question, did you show Dane Mackier (Filledstacks) your take on his architecture in the article: ?

      I kind of like the no-nonsense approach Dane has
      Still using both Get_it (as I recall, he only does this for services) and Provider, while a very simple approach, it understandably gets critiqued (by e.g. Remi author of Provider) for mixing them. Dane’s arguments for doing so are valid imo, using only Provider in his approach makes some of the code verbose and/or more difficult to understand. Using States Rebuilder with its Injector seems even cleaner than Dane’s version. Still, as I mentioned, have not tried States Rebuilder yet, so I’ll reserve my conclusions until I have 🙂

      • My only reservation is that states_rebuilder can seem magical at times without thoroughly reading the documentation first. I use it in one project and I like it better than ChangeNotifier or even MobX.

  • Hi Matej –

    Thanks for the tutorial, it is very good. Question: do you recommend mixing this state management framework with the other known frameworks like Bloc, Provider, etc in the same app or should one choose one and stick to it? I suppose that in order to be consistent and have more maintainable code one should just choose one, right? But do you think states rebuilder covers all the use cases that other frameworks do? Thanks!

  • void submitCityName(BuildContext context, String cityName) {
    final reactiveModel = Injector.getAsReactive();
    (store) => store.getWeather(cityName),

    how can i call this on initstate ?

  • I am new in programming. My question is how to achieve reload function? For example, like when error comes or user want to get new data by refreshing the page.

  • Hi, just done your tutorial, thanks, it helped me. However, with the latest version of states_rebuilder (3.2.0 as of 10th Nov 2020) the parameter “models” doesn’t exist anymore, that line of code should be replaced by:
    observe: () => RM.get(),

  • I have a problem with code formatting in this comment section: put WeatherStore in between chevrons between the get and the ()

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}