The First Step towards a Flutter Architecture
An actionable guide to separating your UI code from your State Code
In this post I go over step 1 of my 4 step framework for introducing an architecture to your code base.
Most engineers have heard of terms like “Architecture”, “Separation of Concerns”, “Single Responsibility” but I haven’t seen many clear guides on actionable ways to apply this. Not being able to apply this from first principles leaves you at the mercy of ideas like “This is the best architecture for your app”
I have a simple system I’ve been using for years to drastically improve codebases over time in incremental steps.
Architecture is just a fancy way of saying “separate your code into components that grow independently of each other”.
In this post you will learn:
The difference between UI and State code
The process of identifying the code that needs to be separated
A short implementation guide to make it more concrete
Not following these steps, especially in Flutter, can make UI hard to maintain really fast. It becomes unreadable which makes it difficult to onboard developers. Which in turn makes it difficult to scale as a team. To avoid that, lets jump into the first topic.
The Difference between UI and State code
Understanding the separation of UI Code and State Code first requires us to know the definition of the two:
UI Code: Code that renders graphics to the device it’s running on. What the user sees and interacts with.
State Code: Code that determines which graphics to render, what state it should be in, and which interactions to execute for the user.
The problem of separating UI code from State code is unique to Games Development and Flutter because everything is code. This presents a very dangerous pitfall, mixing code with different responsibilities into single files or even functions.
It’s common for Flutter code to contain the UI and State in a single file, in fact, that’s how it’s taught at the beginning (which I agree with). The mistake some developers make is failure to extract that state from the UI as it grows larger. To make it clearer, here’s an example of each:
When it’s fine: If your widget has some internal state logic to swap between states or keeps track of small UI items. Things like Image carousels with page indicators, and widgets with checkboxes in them. To put it more clearly, nothing that requires complex conditions to determine the state.
When it’s not fine: A view with multiple text fields where each has a validation message, a validation function, input to keep track of, formatting, enabling / disabling buttons based on state, plus conditional states. When you have something like this all the state logic has to be moved to a different file.
Identifying when and where to apply
Knowing the information is cool, but I like to be actionable. Here’s how I identify which files I’ll be refactoring when I get asked to consult on a code base to help improve developer productivity.
I go through every UI file and rate it. There are 2 ways to do this that I like:
Number of lines of State code: Count the code (in lines) that is not boilerplate (class definitions, widget structure definitions) and not UI code. It’s a metric any developer can use at any experience level.
The number of responsibilities it has: Identify and count all the responsibilities you can see in that file’s code.
I decide, for the project, what my refactor limit should be. This is decided per project and depends on:
What the intention of the project is.
What the time commitment of this project is. How long are we going to be building and expanding this codebase? (2 weeks, 1 month, 1 year, 5 years)
What stage the project is in (Fresh, MVP, alpha, production)
How many developers work on the project (single, team, multiple teams)
Using that refactor limit I identify which files need to be refactored
I order the files from smallest to largest and attempt to refactor 1 or 2 every day. If it’s your full-time focus then I go down the list for however long it takes.
Knowing when and what to refactor is useful, but how do you actually do it? This is how I typically split my state apart:
Move the state into a new file: Take the code that has been identified to be managing all the state and move it into a new file. I use the ViewModel approach so I usually call my file
ViewNameViewModelthe move the file in there.
Make a connection between the two: The reason the state was in the same file is that when it changes, you can call
setStateto rebuild your UI. You need this same functionality when you split your state apart. I use the ChangeNotifier with Provider to create my link. I do it all through Stacked which takes care of this part and allows you to rebuild your UI by calling
notifyListenersin your state object.
Change all references in your UI to use the ViewModel: After moving the code out, make sure to update all references to use your ViewModel.
The last thing is a check to know you’re done. You want to make sure that in your file dedicated to your Flutter UI there is only Flutter UI code and the code to read the state, or interact with the object that has your state. When you reach this point you’ve completed separating your UI code from your State Code.
Repeat this process until your UI is free from its state management and you can independently write unit tests for you state without starting up the Flutter engine. At this point, you’re ready to move on to the next step, Separate your State Code and your Business Logic.
Subscribe to make sure you don’t miss the next post.
If you liked this post, work directly with me.
Book a 1-on-1 call with me to take the next steps for your Flutter project.