[MUSIC PLAYING] MATT SULLIVAN:
Welcome, everyone, and thank you for coming
along to our talk today. We are very happy to see so
many people in the audience, because we spent a long time
thinking about how we could make the title of our talk the
most exciting and click-baity title we could make it. And all we could come up with
was pragmatic state management. So thank you for taking the
time to come and see our talk. My name is Matt Sullivan. FILIP HRACEK: My
name Filip Hracek. We're both developer
advocates on the Flutter team. MATT SULLIVAN:
Yes, and we're here today to talk about
state management and a pragmatic approach
to it in Flutter. So why are we here
talking about state again? We were here last year
talking about state. We've talked about state before. And really, what we
want to do is take you through how an app
typically gets developed. So usually, apps start off with
a very sophisticated product design diagram to tell you
how your app is laid out. And this is given to
developers, and then the next thing that's
expected is you build the app. [LAUGHTER] And you may laugh,
but that probably happens more than
we'd like to admit. And with Flutter, that's not
entirely inconceivable in that with Flutter and Flutter's
way of handling composing UIs together, it's relatively
straightforward to be able to build
out a UI close, if not very, very close, to
what your designers produce, which is all well and good. So am I saying Flutter goes
from napkin to app immediately? Well, you have this problem,
which is interactivity, and how do you handle data
flowing through your app, changing state, widgets
interacting with other widgets. Getting this wrong
at the very beginning is a very costly
mistake to make. And so what we want to do
is arm you with the tools that you need to be able
to make an effective choice at the beginning. Click. There we go. So what are we going
to talk about today? We're going to talk about
what state management means when we talk about
Flutter and why it's really, really important. We're going to give you a
pragmatic means of managing your state, but we're going
to take you through a journey first. Because we have been trying lots
of different ways of handling state here at Google. We've tried several
different approaches, and it's taken us a
while to get to the point where we're at today. So we're going to give
you a little back story, and then we're going to
go, and we're, hopefully, going to arm you with some
tools to be able to do that. So what do we mean
when we say pragmatic? We mean three things. Firstly, your code needs to be
understandable and readable, but fundamentally,
your UI and your app is going to evolve over time. How you manage state
needs to evolve with it. It needs to be
flexible and malleable, so you don't end up
with a spaghetti mess as you iterate on your UI. Testing is important. And sure, it's easy enough
to test state in isolation. But how do you
test state when you want to see its effect
on UI and vice versa? You need an elegant and
easy means to do that. And finally, there is no magical
silver bullet here in terms of if you do this,
your performance will be awesome
every single time. We're going to talk about a
means by which you can build in state that will give
you good performance, but you always need to be
aware of the edge cases. You need to understand where you
need to look out for where this may impact your performance. And we'll talk a
little bit about that. But first and foremost, what
do we mean by state management? FILIP HRACEK: Yes,
so I know what you're thinking how
can this talk get any more exciting, right? And the answer is definitions. So let's talk about
state management. What is state management? Is it the UI state
that you manage? Is it the business logic? Is it the network? Is it the bits on the disk? Is it the pixels on the screen? If you think about
this long enough, you realize that, basically,
all of computer science is you have bits, and you
turn them on and off, right? So state management. So are we going to cover
all of computer science in the next 30 minutes? MATT SULLIVAN: You've got
35 minutes and 42 seconds. FILIP HRACEK: OK, so
let's focus on what most people think about
when they ask about state management in Flutter. And what they mean
is I have a widget, and I want to change its
state from somewhere outside of that widget, either
from another widget, or from the network,
or a system call. So let's say we have
this very simple app. It shows the pie chart, and
there is a slider, right? All we want to do
is change the state of the pie chart in
response to something happening in the slider. If we look at the very
simplified widget tree, we have, basically,
only three widgets-- my home page and then
my slider and my chart. And my slider wants to
change the state of my chart. OK, so in this
next segment, Matt will pretend to be an
inexperienced Flutter developer. And he will try to
show you what we often see with inexperienced
Flutter developers when they first
approach this problem. Take it away. MATT SULLIVAN: This is
called Matt writes code and Filip makes fun of it. So in all seriousness, I'm
going to try to wire some state management together. Can we flip over to
the laptop, please? And we see a lot of people
make some of the mistakes that I'm going to do today. So hopefully, this
will be instructional in terms of some anti-patterns. And so let's look
at what we have. I'm going to start running
my app here in my simulator. So basically, what we
have is a very simple app. And it has a home page,
as Filip pointed out. And it's got a chart,
and it's got a slider. We've got some padding and
whatnot and some layout. But we effectively have
this, and this is great, and nothing really happens. We have a stateful
widget for a slider, and all it's doing at the moment
is when you drag the slider, it updates itself. And then we have a pie
chart class widget here. And all this is doing is it's
using a charting package, which we've built in Google,
to render a pie chart. And it's taking a
little bit of data here. So my job is to work
out how to get my slider to interact with my pie chart. So the first thing I'm doing
is I'm looking at the disk, and I'm like, OK,
well, my chart clearly needs to be stateful, because
I'm going to be changing it. So I'm going to
go up here, and I am going to use our awesome
Visual Studio plugin. And I am going to
make this stateful. So that's great. So now, I have state. But how do I make the
states interact together? I'm looking for the equivalent
of things like find view by ID, so my slider can
talk to my chart. But Flutter doesn't have that. So I think the best
way to do this, Filip, correctly
so, is I'm going to make a variable here
called chart state. And that will let me
access it for my slider. And then down here,
what I can do is-- yeah, I can see that. FILIP HRACEK: I'm OK. I'm OK. MATT SULLIVAN: I can
make this chart state, access my state
here, and then I'm just going to continue
to return my-- FILIP HRACEK: So just
good old global variable. MATT SULLIVAN: It's
not that global. FILIP HRACEK: It's
very global, but OK. MATT SULLIVAN:
And I can't spell. So I'm going to save this. It's probably going to break
my app, because I'm changing from stateful to stateless. That's OK. I can do a hot restart. And what we have now is-- OK, so this is state, but
nothing's happening here. So I'm going to go
over to my slider. And this is now going to get
very easy, because I can go, oh, I've got this on change. It's setting state
for the slider. I can access my chart state, and
I can just call set state here. Because this makes sense. And what I can do now is I
still have access to my chart. Now, what I haven't
done and I forgot to do is now that my state
is going to change, this needs to be a var
rather than a final. And I can access this. And then all I need to do is
use a simple helper function that we have to create data. And look, I can pass on my
value, and I can save this. And we'll do this just so
that all my state is wired up. And lo and behold, I mean,
that's a few lines of code. It's working really nicely. So I think that-- what
are you laughing at? FILIP HRACEK: Yes. OK, let's figure out
what different things are wrong with this example
and with Matt's approach. OK, let's go back to the slide. Thank you for being
very instructional. OK, so let's call this
Matt approach from now on, in general. MATT SULLIVAN: If I
see #MattApproach-- FILIP HRACEK: Yes. So first of all, Matt was
strongly coupling widget, so my slider knew a lot
of things about my chart. And that is not very scalable
if you have a larger app. Secondly, Matt was
globally tracking state, which is never a good idea. It's never a good
idea, especially for a unitless thing. But in this case, for example,
if you had too many charts, then they will somehow
be synchronized in terms of what they
show, so not a great thing. And then lastly, we
are pulling said state from outside the widget. And that is really a bad idea,
because, A, good luck figuring out what exactly
changed your state from where, and secondly,
you could even actually crash your app. So let's not do that. MATT SULLIVAN: So never trust
a line of code I ever write is, I think, what you're
trying to say. FILIP HRACEK: Yeah. What Matt was doing is he was
actively fighting the framework instead of letting it help him. So what do we mean by
letting it help him? First, you need to realize what
all declarative frameworks have in common-- this one, this thing. UI is a function of state. So in our case, the pixels
on the screen or the layout is the state, the current state
of your app run through your build methods. Your build methods declare. That's why we call
it declarative. They declare this is
where the screen should look like at any given
moment, given any given state. So that makes it very
easy and very predictable, and you have only one
code path to every screen. That's what's great
about state management in declarative frameworks. You can see UIs on the left-hand
side of the equation, which means no UI changes other UI. No find view by ID or
anything like that, unless you do something
like Matt did. OK, so what does it look
like in the widget tree? We need something,
some state that will be approachable
or accessible to both my slider and my chart. So we do something that we call
in any declarative framework lifting state up. So we lift state up, and
we have a model that's called my schedule, in our
case, and it's just something that we attach to my home page. Because that's the only
common ancestor, in our case, to both my slider and my chart. And then when the
user taps the screen, my slider sees that tab. And instead of going
to my chart directly, it goes through my schedule. It asks for my schedule and
tells it, hey, you know what? I was tapped, and
this value changed. And then my schedule is
responsible for notifying its listener. So it notifies my chart,
hey, something changed, and my chart redraws. And then it actually goes down
to my slider as well and says, hey, this thing
actually changed, and then it changed
to my slider. So that's it in a nutshell. But that is theory, and we're
talking about pragmatic state management. So what does it
look like in code? A lot of people are asking, hey,
so what package should we use? So let's give you a
little bit of history of state management approaches
at Google for Flutter. So one of the first ones was
something called scoped model. It is still heavily used. It's very simple. It's basically a direct
translation of what I just told you into code. So you have some
model at the top, and then you have descendants. And these descendants
are notified whenever the model is changed. And it's great and, as
I said, it's still used. But it lacks some
of the features that you would want from
something like that. So we'll talk about
the features that we would like to see during
the rest of the talk. Another approach
that we have is BLoC, which was created for AdWords,
which is one of the more complicated apps out there. And it is great. It's based on Rx and streams. But we've heard loud and
clear that for many people, this is either too
complex for their need, or if they're just
coming to Flutter, and they try to find out
about a new approach, they don't want to learn
Flutter and also learn all of Rx and streams
and all of that. So there we were last year. Look at us-- very
young and naive. And by the way,
good job on having the same shirt this year. [LAUGHTER] [APPLAUSE] MATT SULLIVAN: This
is my favorite shirt. FILIP HRACEK: Yeah, I can tell. Yes. MATT SULLIVAN: I do wash
it in between conferences. FILIP HRACEK: Good. Good. MATT SULLIVAN: I have a new hat. FILIP HRACEK: Yeah. AUDIENCE: Yeah! MATT SULLIVAN: Thank you. I have a new hat. [APPLAUSE] FILIP HRACEK: So we could say
that you managed to change the state of your hat at least? MATT SULLIVAN: Oh. [LAUGHTER] FILIP HRACEK: I'm not sorry. OK, so here we were. And we were thinking,
OK, it would be great to have some state
management approach that is closer to scoped model
but has all these features that we would love to see. And lo and behold, the community
came and said, you know what? PackageProvider. PackageProvider is a
community-based effort. It was mostly built
by [INAUDIBLE].. Thank you. We're using it
internally at Google, and it is very close
to scoped model. It is very close to
what I just told you. It just has some nice
bells and whistles that we are going
to talk about soon. So in this next segment,
Matt will vindicate himself from whatever that was
last time he coded. And he will use scoped-- I'm sorry, not
scoped, but Provider to build the app again. So if you could switch to
Matt's screen again, please. MATT SULLIVAN: Vindicate myself. OK, so what you saw before,
why the magical power of Git did not happen-- there we go. And I'm going to get
this going again. So we're back to where we were
15 minutes ago with nothing wired up. So the first thing
Filip talked about was encapsulating your
state and lifting it up. So the first thing we're
going to need to do is encapsulate our state. So what I've done here is I have
this very simple class called my schedule. And you'll notice
that it has a mix-in-- with and dart. Let's just mix in other classes. And this is a change notifier. And what is this
magical change notifier? All it does is it adds listening
capabilities to my schedule. So now, we get add listener,
remove listener, dispose. And hey, I can notify
listeners when things change. I have my state in here now,
which is still our double. Doesn't have to be a double. It could be a lot
of complex state. We only need double here. And I have a getter,
and I have a setter. And the setter calls
notify listeners so that things know when
things have changed. So that's all I have. First thing I need
to do is, where am I going to stick my state? FILIP HRACEK: Lift it up. MATT SULLIVAN: So I'm
going to lift it up. So I can see here, my
chart needs to access it. Slider needs to access it. I could put it in a
column, but I'm just going to stick it up
here above scaffold. So how do I put this in? Well, I'm going to wrap
this in a new widget. And I'm going to use a
change notifier provider from the Provider Package,
which will provide a change notifier up and down the tree. This takes child, so
already have that. And then also, we
need to provide it with the actual state object. And I can do that very
simply by creating a builder. This builder is going
to take context, because they nearly always do. And I'm just going
to instantiate an instance of my schedule. So now I've got some state
in a tree, which is great. But how do I access this state? So let's take a look at slider. I need to wire slider
up to my new state, and there's a couple
of ways of doing it. FILIP HRACEK: So
there are two ways. You either can use the
consumer widget or provider.of. MATT SULLIVAN: OK, so
let's use provider.of. I'm going to get
access to my schedule by calling provider.of. Then I'm going to tell it
what type I'm looking for. And I'm going to
give it the context. And so at this
point, this is going to go up the tree, going where's
the first instance of being provided of my schedule? And it's going to
provide me with that. Does what it says in the 10. Then what I can do is instead
of tracking my state now through my stateful
widget, I can simply do schedule.statemanagementtime. And then to update
it, because this is a closure, which
gives me a value, I can do
schedule.statemanagementtime equals value. I'm going to save that. And let's get rid of this
because this is distracting. And you'll notice that
my state still works. Let's prove that by
doing a hot restart. You'll notice that
my state still works, but I'm no longer tracking
it in the stateful widget. In fact, I could
change my slider now to a stateless widget,
because it doesn't actually need to track its state itself. So this would simplify right
down to the build method. Now I need to wire
it in my pie chart. So I could do provider.of
again, but let's do it slightly differently. This time, I'm going to use
the consumer widget-- consumer. Again, tell it what
you're looking for. And this time, this is going
to take a builder, which is going to provide a few things. The builder is going
to give us a context. Most importantly, it's
going to give us access to our schedule object. It takes an optional child,
which we're not going to use, and that's it. So now I'm wired in. And instead of having my
hard-coded series list, what I can do now is I can
use my helper function to access my
schedule.statemanagement. I'm going to save that. And if I've done
this correctly-- there we go. So all wired up. Am I thus vindicated? FILIP HRACEK: Yes,
I think you are. MATT SULLIVAN: Thank you. There we go. [APPLAUSE] FILIP HRACEK: Back
to slides please. So fun fact-- we actually had
our own package, kind of like scoped model version 2,
and then we released it earlier this year-- open
source and everything. And then we realized, Provider
is actually much better. So we're using Provider instead. So don't use Provide. Use Provider. MATT SULLIVAN: What
we want to point out here is that we don't
want to reinvent the wheel every single time. We're much happier
working with the community to build these things. And now we're working with
the creator of Provider to extend it further. And so why build it ourselves? We're more happy to
deprecate our own stuff. FILIP HRACEK: Absolutely. Yes. So pie charts and
sliders are cool, but let's talk about a
more real-world approach. We have this app,
which some of you may have interacted with here at
I/O. If you haven't, you can go to the Sandbox and try it out. It is also available on both
App Store and Google Play Store. And also, the code is available
in open source on this link. And this app was built from the
very beginning to be very live. There's a lot of state changes. Everything changes all the
time and different pace. And we want to make sure that
the UI reacts to these changes always. So the cool news
is that most of it is basically what
you just saw Matt do. So we have a consumer here
of something called company. And then whenever that
changes, we can do just that. So that's cool. MATT SULLIVAN: Oh, right. Over to me. We rehearsed this, right? So does this now
mean that we can go from our back of
the napkin to creating our fully-fledged app just by
using Provider, and we're done? FILIP HRACEK: No. No. No. And that's the rest of our
talk, talking about the nuances about using something
like that, hopefully, to show you the experience that
we had with Developer request. First is disposables. So many times, your state
wants to clean up after itself. You have a database connection. It needs to be closed. You create a file. It needs to be closed-- all these things. So what do we do there? Fortunately, Provider
actually, by default, is prepared for this. So if you use Provider
of something like foo, you have a builder, but you
also have a dispose callback in which you can dispose of the
object that you're providing. So you know that once
it's not in the tree, it will be disposed of properly. So that's cool. It's even better if
you use something like ChangeNotifierProvider,
because we know now that your user here is an
actual change notifier. Change notifiers do have
their dispose methods. So we can just call it
for you instead of you giving us the callback. So that's what you saw Matt do. And he did not even
need to think about it. It was just made
automatically for him. MATT SULLIVAN: Mm-hmm. So handling state
is great, but you don't want to have a giant, big,
single state class with tons of complex state in there. Typically, you're going to
want to break your state up into different components,
different classes, different objects
in order to make it a little more fine grained. The good news is that with
Provider, you can do just that. So there is a handy widget
called MultiProvider. And you'll notice, when
I was getting access to my schedule state, I
was specifying the type. And you can create multiple
providers for different types. And when you
specify the type, it will search up the tree to
find the appropriate type. So you can compartmentalize
your state into different models and be able to access
them efficiently throughout the tree. FILIP HRACEK: Sometimes
you want to take a more fine-grained approach
to listening to your model. For example, in developer
request, we have this screen. And this screen shows
a list of characters. And the list itself can change,
because we can add a character, for example. But also, each of the
characters can change. And for performance
reasons, we don't want to rebuild the whole thing
every time any of the character changes. So what we can do is what
I call listening to parts. Here, for the
whole screen, we're only listening to
character pool. So all we do is whenever a
character is added or removed, then we rebuild the
whole grid view. That's OK. But then in each of
these grid tiles, we're actually listening to
that character that's shown. So if this character changes, it
won't rebuild the whole screen. It won't rebuild the
other characters. How does it look like in code? You realize that change
notifiers can have other change notifiers in them. So here, we have character pool,
which is a change notifier. It has a list of characters. Those characters are
change notifiers. And in your build
method, then, you first listen to character pool. That will not change very often,
because we're not actually adding or removing too
many characters per frame or whatever. And then in each of the
titles, you get the character from the pool. And then you provide
that character to the rest of the subtree. So now the subtree only
rebuilds when that character rebuilds and not the others. MATT SULLIVAN: So
at this point, you must think we're completely
in love with change notifiers, because that's all we talk
about is change notifiers. I really like
Provider, but I also love managing my
data with streams. And I use a modified
version of the BLoC pattern. And I was a little sad. Provider came
along and I'm like, oh, change notifiers is
nice, but I prefer streams. Well, you know, guess what? There is a stream provider in
the Provider Package, where you can give it a stream,
and it will update when new data comes through. And it's not just streams, and
it's not just change notifiers. You can pretty much
provide anything. So for example, here, this
is a very simple example, where we're just providing an
instance of player details, which, in this case, player
details, once you log in, don't change. And so this won't update. There's no notifications
to say that it's changed. But we can provide this
in our widget tree, and then any of the child
widgets can access that. So change notifiers are great,
but you have the flexibility to use whatever
method you would like or whatever model you
would like in your state. FILIP HRACEK: Measurement--
my favorite topic. So performance measurement--
we know about some things that we can always say about
performance in Flutter. You try to rebuild as
few widgets as possible anytime they change. You don't want to
rebuild the whole tree. We also try to not rebuild more
often than we really need to. These are all nice,
but at some point, you will hit the real world. And then you realize that
it's not all black and white. Sometimes there are drawbacks. Sometimes there are
premature optimizations. Sometimes you just don't know
what works and what doesn't. So what I'm here to say is you
can use Flutter driver, which is our automated framework
that lets you exercise your app automatically, and
you can use Flutter's profiling tools to get the data. And then you can actually
have data on which you can base your next move. So what you see here
is that's my desk. And that's one of the early
versions of Develop Request. And as you can see,
it is completely automatically being played. Each version, we are doing
this, like, 100 times to make sure that we
gather enough data. And what this
allows you to do is that then if there is
a decision before you, and you're not so sure--
like, for example, here, we have a center, which
always notifies listeners. I know that the text
is small, but you don't need to parse all of that. It's a very simple
setter, not optimized. And then we have a hypothesis
that this optimized thing will perform better. We think that, in the setter,
if you check whether or not you've actually
changed the value, and then don't notify
listeners in that case, then it will improve
the performance. But you don't know. That's a lot of new code
that you need to maintain. There could be bugs in it. Do you really want to do this? Also, you're doing more
work in the setter. So maybe that will offset
any kind of advantage that you will have from not
notifying listeners too often. So you don't know. And you can either argue
for hours about this, or you can measure it
and see for yourself. So in this case, I'm happy to
say, it actually did optimize. So the optimized version, if
you measure the CPU time taken by the full exercise,
went down by, I think, something like 12%. And it is statistically
significant, as you can see from
the mustaches there. So that's cool. And now we don't need to
argue about this anymore, and we can go on. What I'm trying to say
here is not that do this. Many times, we did
something similar, and then it did not have an
effect, and we just reverted. So you want to have
some measurements. You want to be able to have
data-driven decisions about it. If you want to know
more about this, I just published
an article called "Performance Testing
in Flutter Apps," and you can use your favorite
search engine to search for it. MATT SULLIVAN: So clearly,
at this point, you're like, Provider is for me. I'm going to use
Provider for everything. All my state is going
to be Provider-driven. Well, Provider is
great, but there are cases where you just want
to keep things super simple. So, for example, on the left
here, we have our widget tree. And you can see, we've
had to lift our state up. Because we have multiple
widgets in the subtree that are dependent on
that state in order to render themselves correctly. And over on the right,
we have a single widget that is the only widget on
which is modified by this state. And this is an
artificial example. You'll see examples
of this everywhere. So for example, Flutter's
animation framework requires that you track the
state of the animation-- animation controllers
and whatnot. And typically,
they're encapsulated into a single widget. So in the case of this, why lift
it up, provide it, and access it down when you can simply
use a stateful widget to encapsulate the
state and to encapsulate your UI in a single place? So a rule of thumb, if
you have to lift state up, because multiple
widgets need it, Provider is a great
way of doing it. But don't use it
needlessly when there's not much point when only a
single widget requires it. FILIP HRACEK: Lastly,
but not least, testing. So you want to test
your app, obviously. The cool thing
about Flutter is it comes with a headless
testing framework. So you can test your widgets in
the context of other widgets. And Provider is a widget, so
you can do something like this. You can pump change
notifier provider, and then test that it works
well with the rest of your code, with the rest of
your widget tree. So that's one thing, and you
should definitely do that. Because that provides
you one facet of testing. But it's also good to note
that the change notifier or whatever you're providing
is just a simple class. Change notifier does
depend on Flutter, but only on change notifier class,
which is like 100 lines of code with comments. So if you want to have a
very contained unit test, then you can do that. Here, we are having
our character, which is a change notifier. We just make sure that
upgrading the character actually increases its level. That's it, and it
works, and it executes in milliseconds and not
hundreds of milliseconds. MATT SULLIVAN: So to
summarize, hopefully, we're giving you an indication of
why it's very, very, very important to choose an
appropriate means of managing state inside your
app from day one. You want to get that right. You don't want to
really mix and match. You want to nail that down
so that your app, as it grows over time, will
be maintainable and understandable. We've taken a journey through a
bunch of different approaches, and we've landed on what we
feel is a pragmatic approach. But scoped model is still great. And for apps that maybe
are not so sophisticated in terms of its state, it's
a perfectly good approach. If you use BLoC, and you love Rx
and streams, fantastic choice. You can have very
fine-grained control. And there's a bunch
of others out there. There's a really good
implementation of Redux. There's multiple
good implementations of Redux for Flutter. And if you're familiar
and comfortable with the Redux
pattern, then that is, again, a
perfectly good choice. But make a choice
and stick with it. And if you're new to
Flutter, or you're new to declarative frameworks
and how to manage state, Provider is a really
solid, good choice to make, which will hold you in
good step as your app grows in complexity. So with that, we would like
to say thank you very much and enjoy the rest of I/O. [MUSIC PLAYING]