Entitas for Flutter
Alternative way of building reactive, testable and easy to evolve apps with Flutter.
This is going to be a very long article, but it will be totally worth it. 🤞
I assume you already have working understanding of Flutter and you might even have seen this presentation by the Flutter team:
This article will be based on examples and concepts from this presentation. It can be help-full to watch this presentation first, but it is not necessary, as I will give a gradual introduction anyways.
First of all, WTF is Entitas?
Entitas is an implementation of Entity Component System pattern (ECS for short), which focus on developer experience. The most popular implementation of Entitas (Entitas-CSharp) earned over 3K stars on Github and is adopted by many game development studios, big and small.
The main principle of a pure ECS application is:
Separation of state from behaviour
Flutter kind of follows this principle, by separating between Stateless and Stateful widgets, but it is a bit clumsy IMHO. For example, here is the naive implementation of Flutter Counter App as discussed in the “Reactive mobile apps” talk:
Companion repository to the "Build reactive mobile apps in Flutter" talk - filiph/state_experiments
Let’s have a walk through the defined classes.
MyApp is a simple stateless widget, which declares the root of the app. All good and dandy! This class just represents the widget hierarchy, completely declarative, clean as a whistle.
MyHomePage is a stateful widget and it is just boilerplate, it defines a “link” to
_MyHomePageState class and enables the caller to provide
title to the state class.
_MyHomePageState is where we mix state an behaviour, even though from the naming, you might assume it only stores state.
That class does store the count in the
_counter variable, but it also defines the incrementation logic in
_increment method and it has to override
build method which provides the logic how to build the child of the stateful widget.
What is wrong with mixing state and behaviour?
First of all, mixing state with behaviour introduces coupling and information hiding, which makes it hard to unit test. It is also easier to build up a rigid application, which will be very hard to evolve (introduce new features). It is however mostly easier to write mixed code and it is also possible to mix state and behaviour in a way, which makes unit testing and refactoring still possible.
In this article we will first implement examples, where the state and behaviour is slightly mixed and than refactor it to be decoupled (as much as possible).
Lets build a counter app with Entitas for Flutter (or EnitasFF for short)
When we build thing in Entitas, or in ECS in general, first thing we need to do is to think about components. Components in ECS are the building blocks of an Entity. They are classes which store the minimal amount of data we need at a time. In case of counter app our first and most important component is:
CountComponent just stores the count
value, which is an
int. It implements the
UniqueComponent, which is just a “marker” class. It tells EntitasFF that this class is a unique component and that this class must be immutable.
Now what is a unique component?
A unique component is a component with a restriction. There can be only one entity instance carrying a component of such type. It’s kind of like a singleton marker in a dependency injection framework. In our case we should have only one count value throughout the app, so it totally makes sense to make this component unique.
If you are still a bit confused about the terms component, entity and unique? Don’t worry the next section will shed more light on those concepts.
buildApp function returns the root widget of the app. But before it does so, it instantiates
EntityManager and sets the count to be
0. The root widget is a
EntityManagerProvider this is a widget defined in EntitasFF which is an
InheritedWidget and it is primarily responsible for providing the instance of
EntityManager to its child widgets.
What is an EntityManager?
In EntitasFF, entities are created and managed through an EntityManager. If you want to compare Entitas with a relational database, than every defined component class represents a column. An entity instance is like a row and entity manager is the table itself. An entity can carry one instance of any defined component class and we can query the entity manager to give us a reference to every entity, which carries a certain component.
Now as we see on the line
3, we can set a unique component directly on the entity manager. The entity manager does internally following:
It looks up, if there is already an entity, which carries given unique component type and if not, it will create a new entity. Than it adds (or replaces) the component on the entity and returns it.
In our particular case
em.setUnique(CountComponent(0)); will create a new entity which holds
Here comes the rest of the app:
MyApp is a stateless widget. On line
20-26 we wrap the Text widget which displays the count in an
EntityObservingWidget. This widget is provided by EntitasFF and has two required parameters:
provideris a function, which given an instance of entity manager, returns an entity, which we will be observing. In our case it is quite simple, we return the unique entity which carries the
builderis a function which given the entity and Flutters
BuildContext, returns a widget. In our case it is the text widget where the data is value of
CountComponenttransformed to a string.
EntityObservingWidget will make sure that, if the entity is ever to change and this means that it gets its components updated, or it was destroyed, than the
builder function is called triggering a re-rendering of it’s children.
This is also why, the only thing that we need to do in
onPressed callback of
FloatingActionButton is to get unique component
CountComponent, take the value and set the
CountComponent with the value increased by one. See lines
31–36. This action is updating the entity which holds the
CountComponent component and successively leads to execution of
builder function on
This is it!
EntityManager is the single point of truth. We don’t need to extend
StatefulWidget anymore and our application is fully reactive.
But we do mix behaviour / changing the state and widget representation logic on lines
31–36. In order to solve this small issue and make it easily unit testable, lets introduce the concept of Systems.
As the name Entity Component System implies, system is a core concept in every ECS implementation. If you have watched the “Reactive mobile apps” presentation mentioned above, or are generally familiar with Redux, you might think of a system as of a simplified reducer.
The soul responsibility of a system is to change state. The state in EntitasFF is reflected in
EntityManager. So a system can query an entity manager for specific entities and change those entities. It also could create or destroy entities.
In Entitas we normally distinguish between four different types of Systems:
- InitSystem, a system which is executed only once on initialisation and which is responsible for creating new entities.
- ExecuteSystem, a system which is executed on every tick. With tick we mean rendering loop. Those systems enable us to implement real time behaviour of the app.
- CleanupSystem, a system which also runs on every tick but those systems are executed after all ExecuteSystem run and are responsible for cleaning up the state.
- ReactiveSystem, reactive system is a decorated execute system. Reactive systems are also executed on every tick, but they have a mechanism of early exit in case there was no relevant state change. The details will be described a few paragraphs later.
Let’s go back to our counter app and how we can refactor it in order to be more testable. As always we start with an introduction of new component:
This is a unique component which represents the increase event. In Redux we separate state from events, in Entitas it is not necessary. State and event is both data, so why separate them?
Now we can refactor the definition of the
FloatingActionButton to this:
onPressed callback is now super simple. It sets a new instance of
IncreaseCountComponent on entity manager. This action either produces a new entity with
IncreaseCountComponent component or updates it. We don’t do any real computations in our view hierarchy declaring code. The computation will happen in a reactive system. But before we look at the systems lets first see how the
buildApp function has evolved:
If you scroll up and compare it with previous implementation of
buildApp function, you will see that
EntityManagerProvider now has a new property —
Remember how I sad than the main responsibility of the
EntityManagerProvider is to provide entity manager to it’s children?
The secondary responsibility of the
EntityManagerProvider is to execute systems, but only if the systems property is set. Internally it uses Flutters
SingleTickerProviderStateMixin, similar how one would implement animations in Flutter.
systems property expects an instance of a RootSystem. A root system is a system which aggregates other systems, so we can build systems hierarchies similar to how we build widget hierarchies in Flutter. In our particular case the hierarchy is super flat, but in more complex scenarios it is better to introduce some hierarchy.
Now lets have a look at the implementation of systems.
InitCounterSystem is an
EntityManagerSystem and implements
InitSystem. A system should extend
EntityManagerSystem if it want to have access to entity manager. The root system will automatically inject one to it’s child systems if they extend this class. The implementation of
InitSystem forces us to override
init method. Which will be called by
EntityManagerProvider on initialisation.
A careful reader might noticed that, we moved
entityManager.setUnique(CountComponent(0)); statement from the
buildApp function into the
InitCounterSystem. This way the responsibility of
buildApp function become a bit cleaner.
IncreaseCounterSystem is the magic sauce, which implements the transformation of the count component. Or it rather performance the same thing in
executeWith method, what the
FloatingActionButton did in
onPressed callback before the refactoring.
Before we can fully understand how
ReactiveSystem work, I need to unpack the concept of
Lets start with entity matcher.
EntityMatcher lets us describe which component should an entity contain in order to be “selected”. It is basically a simple query. We can instantiate an EntityMatcher with three optional properties:
An entity is selected if it has all the component types listed in
all. At least one component type listed in
any and none of the component types listed in
EntityMatcher(all:[A, B], any:[C, D], none:[E]) is a query where we say we are interested in entities which have components
B set, which also has to have
D component present and should not have component
The entity manager has a method:
Group groupMatching(EntityMatcher matcher)
which we can call in order to get all the entities which comply with the provided matcher. The interesting detail about the group is that it is a class which is wrapped around a set of entities, which match the group and it is synchronously updated if we change the state in a way that makes entities:
- leave the group (not match the matcher any more)
- be added to the group, for example if we have a matcher
all:[A, B]an entity which already had the
Acomponent, now if we add component
Bto it the entity will enter the group of
- or just be updated, meaning that the entity is already in the group but underwent a change which was relevant in regards to the group. If we keep same example as before. The entity has components
Band one of those components was replaced. The entity is still in the group, but it was updated.
Because of this synchronous updates, we can keep a reference to the group for a long time and be sure that if we will iterate on those entities, they will have the components that we expect. It also lets us observe the group and be notified if an entity was added, removed or underwent a relevant update.
And this is how the Reactive systems work.
When we define a reactive system, we need to provide two getters:
EntityMatcher get matcher => ...;
GroupChangeEvent get event => ...;
matcher property we define which Group of entities we should observe for changes. And
event property defines which event should be relevant for us as trigger.
GroupChangeEvent is defined as following:
Back to our
IncreaseCounterSystem. The getters we provided say — we want the system to be triggered when
IncreaseCountComponent is added, or exchanged on an entity. In our case this happens each time we press
One more small detail how reactive system is different from an execute system. In reactive system we need to override the
executeWith method. This method gets all the entities passed which have changed and lead to reactive system being triggered. In our current example this List is not important, but we will have other examples where this list will be used.
OK, we are almost done with this example, one last system to go:
This is an execute system. It will count the number of ticks the application is running and will increase the count on every 120th tick, which is every two seconds. Now our small app has real time capabilities 🤯. And as a bonus, they are easily unit testable:
Before we continue with the next example, lets have a short recap.
In this example we learned concepts like
Group and different types of systems. We see that with EntitasFF we can avoid extending StateFullWidget (with all it’s boilerplate) and can decouple the data transformation logic from the widget composition, which makes the transformation logic easily testable even if the transformation should happens over time. In the next sections we will have a look at a more advanced example and how we can implement it with EntitasFF following the “There is no spoon” principle 😜.
Next example in the “Reactive mobile apps” presentation is about a shopping list and shopping cart applications. The app has two screens — shopping list and shopping cart. Where the user can add an item from sopping list to shopping cart, by tapping on the item, there is a shopping cart button on the top right of the shopping list screen, which displays the total amount of items user added to the shopping cart. And if user is taping on this button, the shopping cart screen appears.
The corresponding Github repositories contains multiple implementations of this app where all implementations use a shared set of classes which you can find in the
Companion repository to the "Build reactive mobile apps in Flutter" talk - filiph/state_experiments
We will build our example on vanilla version of the app. But first lets walk through the shared classes and see how many of them we will, need and what will we change.
First of all in the
common folder, we have the model classes. There is a
Product. All those classes represent concepts on a higher level of abstraction. And this is where ECS and Entitas are completely different from the typical object oriented programming. I call it:
“There is no spoon“ principle.
In the ECS we define the data bottom up. We look at actual data we need and do not invent metaphors for it. In the case of shopping list/cart the data can be represented with following set of components:
CartItem are just entities. Catalog is a
Group of entities which match
all:[ProductIdComponent, ProductNameComponent, ColorComponent].
Cart can be represented by a
Group which matches:
all:[ProductIdComponent, ProductNameComponent, ColorComponent, CountComponent]
This means that an entity is part of the cart if it has a CountComponent, which represents how many items of this product user put in the shopping cart.
ProductSquare is the widget which represents a product in the product list on the shopping list screen. Lines
16–20 represent the logic which is needed to add this particular product to the shopping cart. As you can see, we again add data transformation logic in the widget definition, but don’t you worry, we will refactor it, when we will introduce a more advanced use case.
The widget which represents the list of product squares is implemented as following:
4 we see the first appearance of
GroupObservingWidget. It is very similar to
EntityObservingWidget except that it observes a group and not a single entity. In order to define a group we need to provide an
matcher property. The
builder function receives an instance of the group and Flutters
BuilderContext. On Line
8 we create product squares and provide them with
product entity. If we happened to change a name, or a color of the product, or maybe even add or remove products, the
builder function will be triggered leading to re-rendering of the
The initialisation of the app looks as following:
We took over the idea of the products being created after a slight delay from the presentation, showcasing the possibility of populating the
ProductGrid reactively. Otherwise I believe the initialisation is pretty straight forward. And can be moved to an
InitSystem, when we decide to introduce systems.
The App itself is broken down into two classes:
MyApp class is quite boring, it’s just configuration of Flutters
MyHomePage is a wrapper about Flutters
Scaffold. The slightly interesting part for us is on lines
23–28, here we define the
CartButton to be an app bar action. The
CartButton is wrapped in
GroupObservingWidget where we are interested in all entities which have
CountComponent, so we can go through those entities and add up the values of the
CountComponent. And don’t forget, it is an observing widget so it will redraw reactively.
I think at this point we mentioned all the interesting parts of the shopping cart app and it is time to introduce systems and some more complexity to the use case.
The vanilla shopping cart example is missing multiple important features. First feature is for user to be able to remove items form the shopping cart and second to have an actual price on an item. To make matters worse, or rather more real life appropriate, let’s introduce two currencies
EURO and a possibility for the user to to switch between those.
As always we start with introducing new components:
PriceComponent is something we set on the entity while initialising it. A price component has two values,
amount. This means that on initialisation we have products, which are priced in a certain currency and we will need to convert the amount in regards to conversion ratio.
Speaking of selected currency, conversion ratio and converted amount:
Conversion rate component and selected currency are unique components, because those are values which are unique through all the app, they can change but there should be only one instance of them. This is also the big difference between typical singleton pattern and unique entity / component. Singletons are mostly implemented as unique, but also not changeable instances. Unique in Entitas means that there will be zero or one entity holding this component, whithout any other guarantees.
AmountInSelectedCurrencyComponent is set on the product entity and is reactively computed, when we have price and current conversion rate and selected currency set. Here is the reactive system which does the job:
The system is triggered if price or conversion rate, or selected currency is changing. It safely extracts
conversionRates values from entity manager and than iterates over all entities which have a price component and sets the
AmountInSelectedCurrencyComponent with the proper value. It is actually not necessary to set
AmountInSelectedCurrencyComponent, we could also compute this value on the fly in places where we need it, but if we want to follow a single responsibility principle in our code, introduction of such “reactively computed” components is a good thing.
Following the single responsibility principle, we introduce another component:
This component holds the string which represents the price of an item based on currently selected currency:
priceLabel function is not part of
ComputePriceLabelSystem because it will be reused in
ComputeTotalSumLabelSystem. This system will compute the string which represent the total cost for all items in the shopping cart.
Also here we have two distinguished components:
- One for the total amount, which hold the sum as double value. It is computed by iterating over all entities which have
- Another for textual representation of total amount. It takes the
SelectedCurrencyComponentto pass it to
Last but not least, we have a set of components and systems which represent / transform data according to user interaction:
AddToShoppingCartComponent are set directly on the product entities. This way the “event processing” systems can directly iterate on the
entities provided to
With all this reactive systems in place, the stateless widgets can stay completely free of state transformation logic.
The product square widget now looks as following:
MyHomePage widget is also very clean:
buildApp function is only responsible for instantiation of
Have you noticed something new in the
buildApp function? No?! 🤨
systems property is set to be a
ReactiveRootSystem. This is a small optimisation that we can make if all the systems we wrote are not dependent on periodic execution, but rather on state change.
ReactiveRootSystem is a
RootSystem which performs an early exit, in case the state of your application did not change since the last execution.
And that’s it! We are finally done! I hope you enjoyed this super long excursion into building reactive apps with EntitasFF. If you did, please consider to give this article a clap or two.
I will publish EntitasFF and the examples in a few days on my Github. Just want to figure out a few things first. If you are interested in contributing to EntitasFF or build a few examples, please write a comment or reach out to me on twitter.
Thank you for reading!!!
I wrote a second article about EntitasFF