Elementary: a New Approach to Architecture in Flutter Apps

Vlad Konoshenko
Surf
Published in
5 min readDec 27, 2021

Hi everyone! It’s Surf. To implement Clean Architecture in our Flutter projects we’re using our very own solution called Elementary. If you’re already familiar with its ancestor — Model-Widget-WidgetModel (MWWM), you’ll probably have no trouble catching on. Otherwise, buckle up!

My name’s Vlad Konoshenko and I’m a Flutter developer. In this article, we’ll be looking into Elementary: I’ll give you an example of how to separate a presentation layer from business logic and make your codebase cleaner and easier to maintain.

The package is already available at pub.dev and you can check out the source code at GitHub.

What is Elementary

Elementary is a library enabling developers to write apps in accordance with the fundamentals of Clean Architecture where the modules are separated into distinct blocks. This library is based on the Model-View-ViewModel (MVVM) pattern. In the upcoming articles, we’ll get into more detail about what’s under the hood in Elementary. But for now, we’ll show you how easy it is to use it.

The idea behind this library is to split code into different responsibility layers: UI, business logic, and presentation logic. What we get as a result are well-structured independent modules. To get the gist of it, all you have to keep in mind are these three entities:

ElementaryWidget is where the screen layout is put together.

WidgetModel (WM) contains presentation logic.

ElementaryModel contains business logic and component logic.

Graphical representation of interactions between components in Elementary

An example project with Elementary

To appreciate how easy it is to work with Elementary, let’s build a simple weather forecast app. It will only have two screens: the location screen and the forecast screen.

You can check out the finished app in my GitHub repository.

Creating the location screen

Source code at Github

ElementaryModel

In order to handle data, we use a Model class. It has AddressService and AppModel parameters. We accept them as class parameters so that we can easily test the class later.

The service is an object that will process the forecast data. In the context of this example, it doesn’t matter how it’s implemented. AppModel will keep the data relevant for the location selected.

Let’s create a public method to gain access to our repository. We shouldn’t grant access to a repository in WM because it goes against the single responsibility principle. Once it’s ready, the class should look as follows:

Widget Model

Widget Model stores the data necessary for UI. In our case — the search box and the list of cities matching our search parameters.

WM inherits from the WidgetModel class. The model we receive as a parameter should be passed to the super method. This model will then be available as a WidgetModel field, just as shown in the example. We can then refer to it via model.

Let’s also introduce a createSelectAddressWM function that receives context so that we can later get dependencies by means of di and pass them to the model. This function will be called in the UI layer.

ElementaryWidget

We leave all of the layouts in the build method. Note that when we create a widget, we pass the function that creates a WM — the one we wrote in the previous step.

Creating the forecast screen

Source code at Github

ElemetaryModel

The model will contain the forecast service and Location that provides the information on which location is picked. Next, we request the forecast details in the getWeather method.

WidgetModel

In this case, WM is an intermediate layer that provides data to a widget element. It only contains a single method that tries loading data once again in case of an error. To display the current data status, we use EntityStateNotifier. It’s a class that forms a part of the Elementary package and contains certain mechanisms based on the functions of ValueNotifier.

EntityStateNotifier has content, error, and loading methods. With the help of these, we identify a current state without any extra flags. Another abstract class — IWeatherWm — lets us define the values we grant access to on the part of the widget.

ElementaryWidget

This screen contains a standard build, where we use EntityStateNotifierBuilder widget (that comes with the Elementary package).

This widget has 3 crucial callbacks: errorBuilder, loadingBuilder, and builder. Each of them corresponds to a state of EntityStateNotifier in our model. As a result, it allows us to divide the states into separate widgets, thus making our code more readable.

Some practical tips

  1. Pass all dependencies to a model to get all data regarding the current module.
  2. WM is where you have to pass services and wrappers dealing with the context (theme, dialogs, navigation).
  3. Create a system of widgets that will be updated independently from each other with the help of various builders (StreamBuilder, ValueListenableBuilder).
  4. It’s preferable to use ValueChangeNotifiers or streams to avoid any unnecessary interface updates.
  5. If you use interfaces to build a UI, you can’t violate the Law of Demeter. It’s better not to refer to context in UI via wm.context or refer to a model via wm.model.
  6. It’s better to get all styles and localization from WM, thus providing the UI with the relevant information only.
  7. All necessary dependencies have to be passed as parameters so that you can change them when you run tests.
  8. Sometimes a widget can have no data to handle, in which case you can create StubModel and pass it as a stub.

We’re constantly working on this solution to make Flutter development even more convenient. The most recent version is available at pub.dev.

Want to stay on top of app development trends and know how to create apps millions of people love?

Follow Surf on Twitter!

--

--