State Management in Flutter?

Greg Perry
Dec 21, 2020 · 16 min read

More like State access, no? Conceptual discussion.

As you know, there’s a great number of architectural frameworks offered to Flutter developers to supply some form of State Management to their apps. I even threw in an offering that emulates a time-tested design pattern. However, I won’t promote it here. That would be uncouth.

Back in March of 2018, all I wanted was to get rid of two warning messages that I was always encountering when first learning Flutter. These messages were telling me I was doing something wrong and not optimizing my code to take full advantage of Flutter's unique characteristics. You know the messages I’m talking about, don’t you?

‘@immutable’ and ‘setState’

Note, you now have an array of frameworks and architectures available to you to overcome these warning messages. However, as I know now, it all comes down to a separate class for the ‘mutable’ properties of your app and utilizing a subclass of the State class. To be more specific, reliable access to a particular State object so one can call its setState() function and ‘refresh’ that portion of the screen and reflect changes to the app; changes to state.

Separating the interface from everything else that makes for better progress in development as well as better scalability and maintenance down the road.

Now, two years in, I went the long way around and created a whole architectural framework that, if I don’t say so myself, seamlessly works with Flutter while emulating a well-founded design pattern. It’s my chosen approach to software development in Flutter frankly. However, again, not promoting it here.

In fact, today, I’ll give you a stretch of code that I feel will be all you need for most of your Flutter apps. One Dart library file with one class and a couple of mixins will be all you need to serve as your app's State Management.’ I mean, the Flutter team has already provided us with a ‘State Management’ mechanism involving State objects! Despite that, we’ve been slapping on top of Flutter every manner of architectures in an effort, I suspect, to supply developer’s something they’ve worked with before (Time is money after all.). Redux comes to mind. Redux!?

I Like Screenshots. Click For Gists.

No Moving Pictures, No Social Media

Let’s begin.

Other Articles by Greg Perry

As many of my readers know, I usually use well-established examples to demonstrate the topic at hand in my articles. This time, we’re back to the time-honored ‘startup app’ (the counter app) that’s presented to you when you create a new Flutter project. It’s a classic example. However, this time, we’re going to ramp it up a bit to fully demonstrate the little Dart library file I’m presenting, today.

In fact, let’s not keep with the suspense. Let’s display that Dart file right here and now, and get it over with. Most of it is listed below. Mind you, there’s really no need to examine it for too long. It’ll be demonstrated soon enough with the example. I would suggest just scroll down past it (noting how little of it there is) and realize this is all the State Management you’ll need for most of your Flutter apps.


Divide And Code

As I’ve also stated in past articles, there’s one common trait found in all the frameworks offered these days — the access to particular State objects during the execution of your app. More specifically, access to their ever-important setState() function.

Below, listed with the first half of the example app we’re using today, is a gif file demonstrating the functionality of that example app. Note, there’s not one, not two, but three State objects (Flutter pages) that make up this example app. In fact, there are four State objects in all — you’ll see that soon enough. Regardless, there are four ‘states’ being retained and managed. It’s an attempt to convey a typical Flutter app in the guise of a very simple example app, and so it’s a counter app with three counters. Their pages come one after the other. While running, you can go up and down the routing stack. The app is demonstrating the ‘state management’ involved.

The Classic State

Further note, on the second and third pages, you can increment the counters on the previous page! On the third page, for example, there are two buttons to increment both the second page and the home page. Well, that’s neat. Now how is that done? Finally, pressing that ‘Home Page New Key’ found all the way up on page three results in the counter on the first page (the Home page) to be reset to zero! Now, what’s going on there?

Granted, this is such a rudimentary example, but it does demonstrate some fundamental aspects involved in the State Management of a typical Flutter app. I’ll put to you that all the frameworks offered, today, use the same basic mechanism to provide such capabilities.

Use the setState() function to a particular State object.

Let’s examine the first State object responsible for displaying a screen to the user. In this case, it’s the ‘Home page’ greeting the user with the first counter. Below is a screenshot of that State with little red arrows highlighting points of interest. What do you see?

First and foremost, you can see the example app is using a subclass of the State class called, SetState. (Update: SetState is now a Mixin! More useful.) Yes, this class is the main point of interest in this article. Certainly using a subclass of the State class now relieves us of that one annoying warning message about using the setState() function (Update: It’s annoying again! It’s a Mixin!).

You’ll find all the frameworks out there have a ‘subclass’ of the State class in one form or another. All of which supplying a means to use the setState() function to a particular State object — whether the developer knows exactly which State object that is or not is another matter.

What else do you see? Well, there's “the separation of work” that I personally live by when developing code. More specifically, there’s always a separate and dedicated class (_HomePageBloc in this case) that’s responsible for the actual ‘business logic’ involved in the app while the build() function in some widget somewhere is responsible for the interface.

Further, there’s the degree of abstraction that I always implement as the API between these separate areas of responsibility. As an example, the actual ‘counter’ here in this app is concealed by the class property, data. I follow the consistent practice of naming instance fields after the parameter used by the receiving Widget. As it happens, the Text widget’s first parameter is named data. All this being a consistent approach of doing things — dare I say, I’m following a design pattern.


Now, in keeping with the topic at hand, let’s examine two points of interest in particular. What I’ll be presenting from now on will be concepts subtle in nature but very important characteristics to uphold when writing good code.

The three Bloc classes (no direct relation to the BloC design pattern) you’ll find in this example are indeed the ‘Business Logic Components’ for the app. Each has its own little bit of responsibility (their own little bit of ‘state to manage’). They’re also the app’s event handlers — each response to particular events that may be triggered by the user or by the system (the phone) itself.

The screenshot below presents the first Bloc somewhat named after the State object it’s to work with. It even explicitly takes in the type of that State object it works with. At a glance, you can see this Bloc class is for the home screen.


Now, why is the Bloc simply taking in ‘the type’ of the State object? Why not take in the very State object itself? Seems to be another rouned-about way of doing things, no? Lastly, note the Bloc class is found in the same Dart library file with its leading underscore. For demonstration purposes, this was the case. However, in practice, I’d suggest the ‘business logic component’ deservers its own Dart file — and that’s the case with production code.

There’s another question here. Why was an altogether separate function called onPressed() created in the State object? See below.


I mean, in some design patterns offered today, it’s customary to simply find the corresponding ‘onPressed’ VoidCallback function in the Bloc class and use that instead as depicted in the screenshot below.

However, when you do see such functions in a State object, know that this object is now providing an API. Therefore, it must be necessary for external players to also trigger an event in this object. You’ll soon realize who these players are in this particular example app. Hint: Buttons.


Let’s backtrack a bit and take a deep dive into the instantiation of this first Bloc class, _HomePageBloc. In the screenshot below, you can see we’ve jumped ahead a bit to examine this Bloc class. In fact, we’ll examine all three of them. You can see all three Blocs in this app take advantage of inheritance extending from the common parent class, _CounterBloc. After all, they all pretty much do the same thing and so that function is found in one parent class — working with an integer called, counter.

Again, it would have been better to have these Blocs in their own separate Dart library files. For example, if they were in their own file, you’d then be free to extend a different parent class altogether in the future — displaying string ‘word pairs’ let’s say while unaffecting the rest of the app. The parent class, _CounterBloc, should be in its own Dart library file as well preferably with a more generic class name. A degree of abstraction always makes for easier maintenance of an app in production.

Regardless, note the first Bloc class, _HomePageBloc, doesn’t know the type of State object beforehand, and so that type is passed in as a generic type. While in the second Bloc class, _SecondPageBloc, the State type is known and is explicitly specified. Which approach to use, of course, depends on the circumstance. At least, you’re free to use either. You have that option.

Further note, the second class utilizes a factory constructor, and that’s how it retains its count even when you retreat back down the routing stack! In every Flutter app you write, returning to a previous page will remove a Page from the stack, and if it’s represented by a StatefulWidget, that means the StatefulWidget’s State object will be disposed of. Every time. Unless you do something about it.


You’ll note, when it comes to the second page, the ‘State Management’ has been allocated to a separate class altogether and not left to the State object. Following the Singleton design pattern, the _SecondPageBloc class remains in resident memory for the life of the app. Thus keep its counter (its state) and is assigning a brand new State object to itself (the second arrow) whenever a user comes back to that page.

Now, let’s look at the third and final Bloc class, _ThirdPageBloc. Note that the instance field, state, is successfully overridden with a getter. THIS IS HUGE! Do you know why? It’s huge you can successfully override a mutable instance field with an immutable getter! Since a getter is essentially ‘instantiated’ only when it’s first used, you can provide the ‘type of state’, but you’re not obligated to instantiate a reference to that State right then and there! You can wait. Possibly in some situations, the State is not to be instantiated at that point — It may not be available for some reason.

_CounterBloc class

As you know, it does have access to the ever-important setState() function for a particular State object. It takes advantage of that access and even defines its own setState() function — for any other modules to then call to notify the app and reflect a change. Finally, it offers a corresponding dispose() function to be called in its State object’s own dispose() function when the State object itself indeed terminates during the course of the app’s lifecycle. It’s all nice and compact. However, we could do better.


Note, such abilities, on the whole, should be present in any and all modules that are to work with a SetState object in this fashion. Such abilities should be readily available to any class you may define to contain the ‘business logic’ of an app. Such a circumstance would therefore be a good candidate for a mixin, no? A screenshot of that mixin is below.


That parent class, _CounterBloc, has now been changed — focusing truly now on the one lone functional responsibility assigned to it in this particular app. When it increments its counter, it then notifies the rest of the app with the setState() function. It now takes in the mixin using the keyword, with, resulting in code generally being more modular. Further, as you see below, there’s a higher cohesion in the resulting class.


Navigating The State

Pressed For Consistency

The graphic below conveys the lines of communication of what’s occurring here. The State object (View) and its associated Bloc (Controller/Model) are ‘talking’ back and forth. Further, with their ‘public’ functions, these State objects are now accessible, as it were, by external modules.

For better or worse, I’ve decided to include a public onPressed() function in the third-page state object as well — merely a campaign for consistency.


Attain Your State

Note, all but the last State object instantiated is a specific type unique to this app. In other words, this class (this module) has to explicitly know the names of the other classes involved in this app to function. The last instance field, however, is assigned the foundational type, SetState.


Polymorphism is in play here. The first ‘SetState’ object (retrieved by SetState.root)can be anything — we don’t know what from this vantage point. However, we do know it is a ‘SetState’ object. More so, we know it’s a State object, and so we know we can call its setState() function from this class — from this vantage point. What it does when we call this object’s setState() function, we can only guess. We’ll look into what exactly happens next.


A New Home

Let’s look at the two screenshots below. The screenshot on the left-hand side below reveals when you press the ‘Home Page New Key’ button, the ‘app state’ State object calls its setState() function. That setState() function is displayed on the right-hand side includes assigning a new value to the instance field, _homeKey. That field, as you see in the build() function above, is passed as the key to the StatefulWidget, MyHomePage. Calling setState() will run the build() function once again, however this time, with a new key.

Calling the setState() function from the State object, appState — the fourth State object I spoke of, with its the StatefulWidget, _MyApp, being passed to the runApp() function, will cause its build() function to fire again. This means the named parameter, home, receives a StatefulWidget with a brand new key. In turn, this means that the StatefulWidget’s createState() function is fired again and a new State object is created. One that calls its initState() and build() functions again.

Set Your State


Magic In The Mix


I liked it so much, I published it as a package, set_state. ( Update: there’s a new package: state_set ) Wish I wrote this two years ago. I’m certainly going to use this for my smaller projects. It’s all I need to make a responsive and functional app 2 to 3 pages deep. It does in a more concise approach, what all the other frameworks, including my own, offers — a standard approach to accessing the appropriate State object whenever necessary. Try it. I’ll make you a believer yet.


→ Other Stories by Greg Perry

DECODE Flutter on YouTube

Follow Flutter Community on Twitter:

Flutter Community

Articles and Stories from the Flutter Community

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app