Download as pdf or txt
Download as pdf or txt
You are on page 1of 20

Flutter Basics - Going from setState to Architecture

In this tutorial I will be going over how to handle a common async


situation in Flutter, without throwing architectures at the problem.
You’ll learn how things can be done for simplicity and how to evolve that
into something maintainable and sophisticated … over time … IF it needs to
evolve.
I’ve seen some questions about the depth of what Flutter has to offer
simply because most tutorials focus on Building UI’s and making things
look pretty. This is definitely the case and I do it too. It’s fun, and coming
from native development it’s very exciting to work with something like
Flutter. But I have become a bit worried about what’s being taught to
developers first, especially beginners.
I see questions on StackOverflow from people using BloC or Redux and
they don’t even know the basics of a stream, or that they have to
subscribe, or why setState is used or the concept of an inherited widget.
You’ll see them building a viewModel, calling setState in there and then
dispatching setState’s in an action because their UI is not updating. It’s a
bit concerning.
I am an enthusiast of well written apps and trying to write the best code
possible, for the problem at hand. I don’t throw architecture everywhere.
Sometimes it’s not needed, and I mean that.
Getting State in our App
We’ll start by changing the Home widget into a stateful widget from
stateless. Then we’ll add the Future that we’ll call in the initState override.
The Future will wait 1 second then return the data.

So what’s the most basic way of handling this? We’ll store a list of values
in the state locally and call setState when we get the new values.
Let's add some UI to display the list so that we can see the data. Change
your build function to this.
At this point you'll have a list of results on the screen. I’m gonna add a
little bit of styling to make things easier to look at then we can continue.
Move the ListItemUi out of the builder method into it’s own function. Set
the scaffold background color to grey[900] and use the code below for
your _getListItemUi.

Now back to the actual tutorial. We’ll tackle state feedback while the
setup is simple. So what states do we have?
Busy: We have to tell the user when we’re busy with something. While
we’re fetching the data we’ll show a progress indicator.
DataRetrieved(already taken care of): Show the data to the user when it
arrives.
ErrorOccurred: Show a message when something went wrong
NoData: Indicate to the user that the request was successful but they have
no data to display yet.
Let’s tackle busy first. We’ll increase the time delay to 2 seconds so we
can see the UI in action. We can determine the state using a private
property that checks if the data is null. Based on this value, return a
loading indicator or the list view. We have to make sure the data is null
when we start, so remove the initialisation code and leave it null.
After these changes when you restart your app you should see a busy
indication for 2 seconds then the results pop in. Nice. Onto the next state.
Let’s do the Error state. We’re limited with this approach, but we’ll make it
work. When there’s an error we’ll populate with one item that has the
error message in it. We’ll catch the error after the .then call. We’ll also
have to update the future to return an error. We’ll do that first.

Then we can pass in true in the initFunction call and catch the error.
Awesome. When you restart the app now you should see a little bit of
loading and then the error popping up. Last state is when there’s no info
returned. We’ll update the Future again to take in another additional
boolean that will return an empty list for us.

If there’s no items in the data returned we’ll add one with a message in it.
Add this logic where we get the data back.

Look at that. Using only setState and getting a bullet proof


implementation for async behavior. No packages or architectures needed
for something this simple. That’s why it exists. So what’s the limitation of
this approach?
We have to always call setState to make sure the state updates. This is
not a reactive implementation, Meaning ,we have to write the code to
update the UI in setState. This means more code, more things to keep
track of and extending or adding new pieces of logic would require more
setStates all over the place.
Flutter provides a great way for us to handle all of this without needing to
use a stateful widget and set state. All of the code above can be replaced
by one widget.
FutureBuilder
The FutureBuilder widget is a widget that takes in a Future and allows you
to return UI based on that Future’s state or information. You can provide it
with a builder where you’ll receive a snapshot (information about the
Future’s state) and based on that snapshot you can return the appropriate
UI.
We’ll start by changing the Home widget back to a stateless widget and
remove the initState call. We will also move all of the UI logic into the
builder method of the future.
Move the code from the body into a function called _getDataUi() so we
don’t lose it. Change the widget back to a stateless widget and from the
build method return a FutureBuilder in the body of the scaffold. Update
the view to look like this.

Let’s Pass in the future to the FutureBuilder and supply it with an empty
builder. Like so.
Now we’re in business! We can return the UI’s we want and determine
state locally in the future builder. We’ll start with 2 basic states, busy and
dataFetched. Cut the code from the _getDataUI function and return it from
the builder function.
When there’s no data return the busy state, when there’s data return the
ListView builder. Use the snapshot.data instead of the _pageData variable
so you also have to pass that into the _getListItemUi function.
Now we can handle errors and empty data. Add the error handling right at
the top and the empty check just before the list view is returned. Both of
these will have the same styling so create a method called
_getInformationMessage(). This function takes in some text and displays
that.
Then Change the FutureBuilder to look like this.

With that you should have all the same functionality without using
setState. Pretty cool right. You can test out the states by passing in
different values to the _getListData function. pass in hasError: true and
restart the app and you’ll see the error message, the same with the
hasData: false. There’s still some limitations to using it this way.
You can’t re-run the future builder and go through all the states again. Let
me show you what I mean. Say for instance we got the data but realized
that it’s old so we want to update it. Very common scenario. We’ll add a
FloatingActionButton to the scaffold and when tapped fire off the Future
to show the limitation.
Above the backgroundColor in the scaffold add a floatingActionButton.

That’s a pointless button there, but it’s just to prove a point. If we wanted
to re-run the future to do a refresh then we wouldn’t be able to. Not with
this approach. To get something like this we need the UI to respond to
state changes consistently based on state values we pass it. For that we
can use a Stream and a StreamBuilder.
StreamBuilder
This widget allows you to return a UI based on the values you send to it
using a stream. The way this will benefit us is that we can send the state
values whenever we want and the widget will display the UI we defined
for that state. This means we can set it back to busy, fetch new data then
tell the stream we’re done and show updated data.
To start off we’ll use an enum to represent the states. Create one at the
top of the home file, outside the class, called HomeViewState. Leave out
Error because the stream controller allows you to add an error to the
stream.
enum HomeViewState { Busy, DataRetrieved, NoData }
The way to add values onto a Stream is using StreamController. Create a
Stream Controller, change the FutureBuilder to a StreamBuilder and
change the future parameter to stream.
As you can see the builder can still be used with the same UI. The only
difference will be that instead of using the data to make decisions we’ll be
looking at the enum values. Leave error handling as is and just update the
rest.

Now there’s still some things left.

1. We have to get the listItems for the ListView builder from a different
place, it’s not in the snapshot anymore.
2. We have to emit the stream values
3. We have to run the future when we land on the page. For this we’ll
need to go back to a stateful widget and use the init function.

If you have the Flutter + Dart extensions installed in VS Code then it


should be quick. Ctrl + Shift + R on the StatelessWidget class type and
select convert to stateless . Option + Shift + R on Mac. Now that you have
a stateful Home widget we can continue.
We’ll start by adding the list of items back into the class and calling
_getListData in the init function. We’ll also emit the correct values over the
stream from the _getListDataFuture.

Remember to remove the local listItems value from the builder function in
the StreamBuilder.
The part we want to focus on now is what happens when our Future is
called. At the beginning we broadcast the busy state. If there’s an error we
add that to the stream, if we have noData we broadcast that and at the
end we tell the stream the data is fetched. At this point, even though our
business logic code is still in the same file as the UI, it’s already decoupled.
If you run the code now everything should still be the same, with one
addition. If you tap the FloatingActionButton the view will refresh :) . Now
that it’s all decoupled and in a stream we can create an “architecture”, and
by that I mean split your files up.
Splitting your files, logically
At this point the code for the UI is completely separate from the business
logic. The next logical step, for those that do think about architecture and
code maintenance, is to split up our file.
We’ll split our file into a Model and a view. Create a new file called
home_model.dart and move the following code in there.
In the home file you can now remove all the member variables and replace
it with one model instance. Then replace the calls to _getListData with
model.getListData and the stream with model.homeState. And there you
have it, a reactive-“architecture” for a simple app. There’s no name for this
architecture. But the app is architected. That’s all that I wanted to show.
Please leave some claps if you gained any insight from this, I would really
appreciate it. Youtube video coming out next week, a subscription would
be appreciated. I’ll be doing more Flutter Foundation series posts so
follow me to get them in your feed. If you have something you can’t wrap
your head around or struggle with let me know and I’ll add that to the list
of Foundation articles I want to write.
This is the end of the tutorial but I’d like to mention some things you can
do (as a young architect) to get on your own journey to building well
written, easy to maintain mobile apps using Flutter. Without forcing
certain architectures onto your apps.
Put your files in folders: Keep things neat by grouping your files, no need
for a deep structure just basic. views, viewmodels, models, services, etc.
Decide on a naming convention and stick to it: Here we called our model
a model, but that might clash with the data models we’ll use to represent
our information. You’ll have to come up with a convention to easily identify
the following things in your code.
● View files (The file representing one page/view in your app, not the
separate components)
● View State Models (The file that performs the business logic and
provides state, view-model is a common name, BLoC is being
thrown around now too, Controller is popular as well or just Model)
● Data Models (The file that represents the structured data in your
project). You have to think about this because lets say you have an
app where you can file Reports. You might name your view
report.dart, and when you make your model you want the simplest
name so you call your model for report report.dart also. This
obviously won’t work. But you can name your view report_view.dart,
and leave the model as the simple one, or vice versa. But you have to
stick to it. The rest of the architecture rules you can establish as you
go along. Things like, only the model can add to stream through
actions (BLoC), no two-way binding. Or, have two-way binding, or
instead of having multiple models we’ll have one that represents our
App State and we’ll send messages to it through a stream using
actions.

Whatever you decide, make sure it solves your problem first. Then look at
long term enjoyment of using that code base. If your code is separated
well from the beginning you can quickly change architectures and move
your logic around so don’t worry too much about choosing the perfect one
at the beginning.

You might also like