The damper in Taipei 101 is a sphere made of stacks of flat slabs.

Stacked architecture in Flutter

In the quest of finding the most versatile architecture that I can use to create and maintain Flutter apps. I have tried no architecture, Redux, BLoC, InheritedWidget and Provider. Most of them provide unique ways to structure and manage states of widgets and apps. But most of them contains flaws that prevent full utilisation. An example would be Redux, a way to manage app state and very popular among people who did React. However, the boilerplate code that is required to make a Redux app works is a turn-off, and it has a steep learning curve that prevents people who are unfamiliar with the concept of Flux to get up to speed to work on the app quickly.

I came across this website, and YouTube channel called FilledStack by Dane Mackier. He has the most comprehensive tutorial on written and video form. In one of his video, he introduced an architecture that he developed called stacked—previously known as the provider_architecture. Stacked is a Flutter MVVM architecture that is flexible, easy to use, very maintainable and highly testable. It allows my team members who haven’t touched a line of Dart and Flutter to get up to speed within a short time to contribute their code cleanly and consistently.

The directories are arranged in a very orderly and predictable manner, as shown below.

Alongside this, the library creator also provides multiple functionalities that make development much easy. It is known as stacked_services. It contains implementations of Navigation, Dialog and Snackbar that go perfectly with the Stacked architecture.

In this architecture, it contains three aspects:

I will explain them in reverse order.

Services

Their specific functionalities define services. It can be quite flexible and advantageous in encapsulating certain functionalities in a class.

For example, FirestoreService is a service that contains all the Firestore related code. Or DatabaseService where your database code resides. Services in stacked are created and shared with the get_it service locator.

In the stacked architecture, the get_it service locator is assigned to a global app variable called locator so that we can use it like such.

final SomeService someService = locator<SomeService>();

In addition to this, in case there is a need for ViewModel to acquire the latest changes to some variables inside Service. stacked provides a solution where the service will implement the ReactiveServiceMixin.

Here’s a sample of a Service where Firebase Dynamic Link is implemented.

ViewModel

This is where the logic for the View resides. The most basic ViewModel extends the BaseViewModel. The magic sauce to update the view is via the notifyListener() where it informs the view to react to the values changed in the ViewModel. Moreover, ViewModel can use services via the service locator to access for more functionalities whenever it is required.

Besides the vanilla BaseViewModel. Different types of ViewModels can be extended from. They are:

View

This is where the UI code resides. In stacked, StatelessWidget will always be used for screens and views. The ViewModel handles the State and logic. For this to happen, we will need to use the ViewModelBuilder to connect the View to the ViewModel. Code is shown below.

Misc

For some of the utilities that require StatefulWidget to work. Such as ScrollController, TextEditingController and so on. Stacked provides a solution in the form of HookViewModelWidget which use Flutter Hooks to manage UI logic instead of a StatefulWidget. Hooks provides useful utilities such as useTextEditingController which we can use in the build function before returning the Widget. Flutter Hooks helps the process of initialization, disposal and state tracking of the UI utilities.

In the example below, a login page is bound to the LoginViewModel. We will need to use a TextEditingController to obtain the text input by the user, but TextEditingController won’t work in a StatelessWidget. To avoid using a StatefulWidget, we can use Flutter Hooks to help in this regards. This is shown in the example given below.

Service Locator

The service locator is provided by the library called get_it. It allows us to write clean code and to access objects easily in any of the classes. It is made even better via injectable generator that generates the glue code that we needed to use the service locator in our app. Services in the stacked architecture is a great use case of the get_it service locator.

Example of the usage of the get_it service locator and the injectable code generator is shown below. First, we annotate our example service class with a @lazySingleton annotation.

@lazySingleton
class NetworkService {
...
}

Afterwards, all we need to do is to run the build_runner via

flutter pub run build_runner build --delete-conflicting-outputs

Afterwarthe a locator.config.dart file is generated by the build_runner, which contains all the glue code to use the service provider. It is in the directory where you created the locator.dart file.

Setting up the service locator is quite a straightforward process. We create a locator.dart file with the contents as below.

import 'package:get_it/get_it.dart';
import 'locator.config.dart';
import 'package:injectable/injectable.dart';

final locator = GetIt.instance;

@injectableInit
void setupLocator() => $initGetIt(locator);

And then we call the setupLocator function in the main function before the MaterialApp is returned.

void main() {
// pre-launch setup and config
setupLocator();

// launch app in portrait mode
runApp(App());
}

Bonus: To use stacked_service provided by the same developer who created architecture. We can register the services as shown below.

@module
abstract class ThirdPartyServicesModule {
@lazySingleton
NavigationService get navigationService;
@lazySingleton
DialogService get dialogService;
@lazySingleton
SnackbarService get snackbarService;
}

Stacked Services

Besides the MVVM architecture provided by Services, ViewModels and View. Stacked comes with utilities that help with crucial functionalities of an app such as Navigation, Dialog, and Snackbar. To include these utilities. We need to import stacked_services.

stacked_services: ^0.5.4+5

Navigation

The NavigationService provided by the stacked_services library can be utilised by setting the navigatorKey parameter provided by the NavigationService to the MaterialApp. The views that we created are set in the MaterialAutoRouter annotation of the $Router class in the router.dart file. MaterialAutoRouter contains the parameter to accept AutoRoute. They are shown in the example below.

Once we have updated the router. Dart file. We will run to generate the router code by running

flutter pub run build_runner build --delete-conflicting-outputs

A router.gr.dart that contains the view routes and view params are generated as shown in the image below.

To use the generated class. We only need to implement the NavigationService’s navigateTo function. The implementation of the routes is aided by the autocomplete feature of Visual Studio Code or Android Studio.

await _navigationService.navigateTo(Routes.loginView);

Create a Dialog in the ViewModel

final _dialogService = locator<DialogService>();showRegularDialog() async {
await _dialogService.showDialog(
title: "Hello",
description: "This is a dialog",
dialogPlatform: DialogPlatform.Material,
);
}

Customized Dialogs

For the usage of Dialog in ViewModel. Stacked provides DialogService to achieve this process. To set it up.

We’ll need to determine a key to identify the specific Dialog. In our case, we use an enum.

Afterwards, we will create a setup dialog helper function to register the custom dialog UI with the enum we created as the keys. Here we’ll create our customised looks and feel of the dialog.

Next, we’ll call the setup dialog helper function in the main method before runApp function that runs your Cupertino or Material app.

Finally, we call the _dialogService.showCustomDialog(…) function with input data such as title, description, mainButtonTitle and customData. We’ll use the keys that we have registered during the setting up of the dialog in the variant key. We’ll have a custom dialog that gets called from the ViewModel.

Conclusion

I am here to tell you that I am a happy Flutter developer, especially with the discovery of this architecture where things are predictable, clean and concise.

Another aspect that I love about this architecture is that it works with other third party libraries very well. It works wonderfully with auto_route, a library that generates all the classes and utilities for navigating between views. Other than that, it also works wonderfully with get_it and injectable, which are the service locator and code generator for generating the service locator glue code.

I believe I only scratch the surface of this wonderful library. Go check out stacked. And check out FilledStack on YouTube and his blog. You won’t regret it.

Shameless plug

A shameless plug for the company that I’m working for. If you are looking to join a team of a highly motivated team of developers that is based in Singapore that do awesome stuffs with various technologies (Flutter included). Send me an email at juntung@netvirta.com.

Mobile developer; Netvirta; Android; Flutter; Gradle; Kotlin

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store