Entitas for Flutter

Alternative way of building reactive, testable and easy to evolve apps with Flutter.

Maxim Zaks
15 min readApr 10, 2019

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:

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.

Now _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.

The 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 CountComponent with value 0.

Here comes the rest of the app:

The class 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:

  • provider is 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 CountComponent
  • builder is 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 CountComponent transformed 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 EntityObservingWidget.

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:

  1. InitSystem, a system which is executed only once on initialisation and which is responsible for creating new entities.
  2. 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.
  3. 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.
  4. 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 — systems.

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.

The 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.

The system 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 Group and EntityMatcher.

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:

  1. List<Type> all
  2. List<Type> any
  3. List<Type> none

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 none. So EntityMatcher(all:[A, B], any:[C, D], none:[E]) is a query where we say we are interested in entities which have components A and B set, which also has to have C or D component present and should not have component E.

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 A component, now if we add component B to it the entity will enter the group of all:[A, B]
  • 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 A and B and 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 => ...;

With 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 FloatingActionButton.

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 Entity, Component, UniqueComponent, EntityManager, EntityManagerProvider, EntityObservingWidget, EntityMatcher, 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 common folder:

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 Cart class, CartItem, Catalog and 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:

Product and 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:

On line 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 EntityMatcher as 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 GridView.

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 MaterialApp widget. 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 ProductNameComponent and 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 USD and 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, currency and 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 selectedCurrency and 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 CountComponent and AmountInSeletectedCurrencyComponent
  • Another for textual representation of total amount. It takes the TotalAmountComponent and SelectedCurrencyComponent to pass it to priceLabel function

Last but not least, we have a set of components and systems which represent / transform data according to user interaction:

RemoveFromShoppingCartComponent and AddToShoppingCartComponent are set directly on the product entities. This way the “event processing” systems can directly iterate on the entities provided to executeWith method.

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:

And buildApp function is only responsible for instantiation of EntityManagerProvider:

Have you noticed something new in the buildApp function? No?! 🤨

The 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!!!

--

--

Maxim Zaks
Maxim Zaks

Written by Maxim Zaks

Tells computers how to waste electricity. Hopefully in efficient, or at least useful way.

Responses (2)