The power of tick

Following the single responsibility principal with Entitas for Flutter

Maxim Zaks
6 min readApr 24, 2019

This is the third article about Entitas for Flutter, If you are familiar with Entity Component System (ECS) pattern, or even with Entitas than go ahead, otherwise you might want to read the previous articles first:

In the second article “Services in Entitas for Flutter” I introduced an example app, where user can search for repositories on Github. In this article we will extend this example, to perform search, while user is typing. We will also introduce a caching mechanism, where only last 20 search results are stored.

All those features will be implemented purely with EntitasFF (Entitas for Flutter) without introducing any third party dependency.

First of all, lets have a look at our old Github Service again:

The responsibility of this service seems to be clear — it handles the querying of the repositories and transforming it to entities. If we want to introduce caching of results, this service is the place to introduce the cache. However if we look closely, we see that there are some responsibilities which could be moved around a bit:

  • Setting SearchStateComponent is sprinkled all around the place
  • Deleting entities which represent the previous search and creating new entities which represent the result of the new search are also questionable
  • Error handling and JSON decode is part of the service, maybe it should not be? 🤷‍♂️

In ECS, the way to decompose and follow along the single responsibility principle is simple, you just need to introduce more components and move the logic to systems. This idea also applies to our current problem, but first, let me show you how the refactored GithubService looks after the refactoring and addition of caching feature:

The class itself become a bit shorter. Now when the search method is called, we check the result cache first. If it is empty we create a new entity, perform fetching over HTTP and set SearchResultComponent to the result entity with the term and fetched repositories. In case we had an error fetching the results, we set SearchErrorComponent to our result entity. Than we flag our result entity as current result and set the TickComponent.

Before we go ahead and talk about the Tick. Let me briefly explain what EntityMap (line 5 and 13) is all about.

The standard entity querying in ECS is all about component types. We normally say — give me all entities which have component A or maybe give me all entites which have component A and B and C or D. For this kind of querying we have groups and entity matchers in Entitas. However sometimes it is important to query for an entity, which has a specific value of a component. For this we can use an EntityMap in EntitasFF (in other Entitas implementations we call them EntityIndex). EntityMap is a class which you provide an EntityManager instance and a key producer function. The key producer function is a function, which given a component type can produce a key for a HashMap. In our particular case we take the SearchResultComponent:

class SearchResultComponent implements Component {
final String searchTerm;
final List<Map<String, Object>> repositories;
SearchResultComponent(this.repositories, this.searchTerm);
}

and use the searchTerm property as the key. This way we can find an entity, which have a particular searchTerm value in the SearchResultComponent.

One more thing I want to add here, EntityMap is a one to one map — for every key there can be only one entity. If you will introduce a second entity that produces the same key EntityMap will throw and exception. If however you need to have multiple entities mapping to the same key you should use EntityMultiMap instead.

Enough with the detour let go back to the Tick

In order to implement the search while user is typing and keep only the latest 20 results in the cache, we introduced two components CurrentTickComponent and TickComponent:

Current tick is basically the number of rendering frames that we executed since the app start. And it is managed through this system:

CurrentTickComponent is a unique component which represents the current tick. TickComponent is just a component, which we can add to any entity, like for example in GithubService we add TickComponent to the result entity to identify, when this result was used last.

You might wonder why don’t we use just DateTime, or some kind of a time stamp?

We could also have a time stamp component, but tick is more deterministic and reliable. A time stamp is based on device clock, which can be changed by the user and it also can be problematic while debugging. When you hit a break point and step through the code the time passes, but the tick stays frozen. It is also much simpler to unit test business logic if it relies on a tick component rather than DateTime instance and system clock.

I think now it is time to unveil the search screen widget:

As you can see in this implementation, we search for the term not only in onSubmitted callback but also in onChanged callback, which means that we perform search on every key stroke.

However the search is not performed directly (see lines 6-10). Instead of calling the GithubService, like we did in the previous article, we set the unique SearchTermComponent which contains the text. And we also set the TickComponent to the CurrentTickComponent value. Calling the actual search is done in the ScheduleSearchSystem:

It is an execute system, which checks if there is an entity present, which hold a SearchTermComponent and if it is older than 20 ticks. If it is, we get the search term and call the search method on GithubService. After that, we destroy the search term entity.

The search method will create, or identify search result entity we need an set a CurrentResultFlagComponent on it. This will trigger the ProcessResultsSystem:

This systems takes over the responsibility to destroy previous repository entities and create new once according to the result.

If the query was not successful, search method on GithubService would set SearchErrorComponent on result entity. Which in term will be picked up by ProcessErrorSystem:

Last but not least, we told that we would like to keep only 20 last search results in cache. This is the responsibility of RemoveOldResultsSystem:

It checks if number of entities which hold SearchResultComponent is bigger than 20. If so it finds the oldest entry and deletes it.

And this is it, we introduced a few components and systems. And by doing just that, we were able to narrow responsibility of GithubService and SearchScreen, we were also able to introduce some more functionality which is driven purely by systems.

Isn’t it all a little bit of an overkill though?

We could have introduced Rx and use debounce in order to delay the search method call on GithubService inside of the SearchScreen widget. We could also introduce some kind of an LRU cache implementation in order to keep only 20 last search results in GithubService.

Instead we introduced a concept of tick to the app, which we can use in other places in our code and we don’t depend on third party libraries, except for EntitasFF.

I personally like to keep dependency tree of my applications as shallow as posible. However at the end of the day, it is up to you to decide which way suits you best. My single responsibility is to show a possible solution for a common problem. 😉

Thank you very much for taking the time and reading this article. If you are interested in poking the code, you can find it on Github:

As always, I would appreciate a clap or two. 🙇‍♂️

--

--

Maxim Zaks
Maxim Zaks

Written by Maxim Zaks

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

No responses yet