Using Upstate with any kind of State Object

Jonathan Aird
4 min readMay 16, 2020

--

Flutter state management should be simple (and extensible)!

Upstate, my new state management library for Flutter, is meant to be very easy to use and yet extensible when you need that extra flexibility and power. With version 0.6.0 you can now use Upstate with custom state objects. Before we get into the details, lets go over some design principles I used to build Upstate:

Design

Upstate is meant to emulate Flutter in that it has what I call cascading levels of complexity. What do I mean by this? Let’s look at this diagram:

When you’re starting out with Flutter, you can easily create a standard Material app using the default theme and built-in widgets. This is great for prototyping. If you’re looking for a more specific feel for your app, you can start customizing your theme and widgets. If that’s not enough you can start creating your own widgets etc. At any point in our complexity tree, you can stop going deeper and stay at the level you’re at. This makes Flutter amazing for both new developers and pros. You could even go so far as forking Flutter to change how it works, although hopefully you won’t ever need to.

It’s worth noting that while you can create a standard Material app using only built-in widgets, it’s probably not recommended. Your users will probably want a branded experience and UI that suits your particular functionality.

I designed Upstate to be the same way. It is meant to default to the easy way of doing things and then provide the option for more advanced usage if and when you need it:

Upstate, like Flutter, provides a continuous workflow all the way from the prototyping stage to building a production app. Gone are the days of storing your state in an inherited widget or (god forbid) a global variable and then writing a bunch of boilerplate to plug in a state management library when that simply can’t scale anymore. But even at the most complex level, Upstate is still lightweight and requires very little boilerplate.

Create Your Own State Object

Before we create our state object, let’s first define a state model to be held in our state. We will use a counter example:

class CounterModel extends StateElement {
int _count = 0;
CounterModel(StateElement parent) : super(parent);

int get count => _count;

void increment() {
_count++;
notifyChange();
}
}

Simple enough! It just holds a value, exposes the value and provides a function to increment the value. Note that since we’re using a custom state object, we don’t need to define a converter or data model. You’ll see why. To create a custom state object we create a subclass of RootStateElement

class MyStateObject extends RootStateElement {
CounterModel _counter;
MyStateObject() {
_counter = CounterModel(this);
}
CounterModel get counter => _counter; static MyStateObject of<T extends StateWidget>(BuildContext context) =>context.dependOnInheritedWidgetOfExactType<T>()?.state; @override
void unmount() {
notifyRemovedFromState();
_counter.notifyRemovedFromState();
}
}

Note that we need to initialize our counter model in the body of the state object initializer so we can provide it as a parent to the model. Unmount is called if the entire state object gets replaced with a new one in the widget tree.

Using our state

We insert our state into the widget tree in the same way as before:

StateWidget(
state: MyStateObject(),
child: MyApp(),
)

To get our state we use the same syntax as before:

var state = MyStateObject.of(context);

To create a widget that rebuilds on state changes we use a custom state builder:

CustomStateBuilder(
elements: [MyStateObject.of(context).counter],
builder: (BuildContext context) {
var state = MyStateObject.of(context);
return Text(
state.counter.count.toString(),
style: Theme.of(context).textTheme.headline4,
);
});

Note that while StateBuilder takes a list of paths to subscribe to, CustomStateBuilder just takes a list of state element. This means you can actually use this with elements from as many different state objects as you want. Since you have the build context in the builder, you can get any state object in that branch of the widget tree and even multiple different state objects.

Also note that if you use a custom state object, you never have to use StatePaths or [] operators at all if you don’t want to! You will also get automatic type safety and code completion suggestions. If you want to, you can mix and match a custom state object with deeply nested state lists and state maps. It’s all up to you.

Cheers and happy coding!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response