Developing a Multi-Tenant Architecture for SmartFM using Flutter BLoC

Riaz A
6 min readJul 22, 2023

--

You want to build a mobile app that serves multiple tenants, but you have to build it in a maintainable and readable fashion that complies with as many SWE principles as possible. How do you do it? I used Flutter BLoC.

What is Flutter BLoC?

Flutter BLoC (Business Logic Component) is a design paradigm for managing states in a Flutter app.

Flutter BLoC Overview

There are 3 main layers to this architecture:
1. UI Layer: Handles the presentation layer for user interaction
2. Logic Layer: Handles logic for triggered events and updates state
3. Data Layer: Handles data fetching and data modelling

This separation allows for developing the different layers independently as well as following SRP (Single Responsibility Principle) where each layer plays its own role.

How about the project structure? I decided to use a feature-first approach such that each feature follows BLoC. This allows for easy extension of additional features. This is how I structured my project for each feature:

feature 1
│─── sub_feature_1
│ │─── bloc
│ │ │─── sub_feature_1_cubit.dart
│ │ └─── sub_feature_1_state.dart
│ │─── data
│ │ │─── sub_feature_1_data_provider.dart
│ │ │─── sub_feature_1_model.dart
│ │ └─── sub_feature_1_repository.dart
│ │─── presentation
│ │ │─── widgets
│ │ │ └─── helper_widget_1.dart
│ │ │ ...
│ │ └─── sub_feature_1_body.dart
│ └─── sub_feature_1_page.dart
└─── sub_feature_2
...

Why Flutter BLoC?

In my quest to find the perfect architecture for my project, I researched on about 20 different flutter design patterns. Most of these design patterns were conceptually sound and practical for small-scale apps. However, they were difficult to scale with. SmartFM is a medium-scale app that serves multiple tenants. Hence, flutter BLoC was chosen for its ability to scale and standardize the architecture at a feature-level across all tenants.

However, soon I realized there is no such thing as a perfect architecture. It was only a matter of does the pros outweigh the cons for my use-case. The downside to flutter BLoC is that it is not a beginner-friendly concept, and it requires the use of advanced flutter concepts such as streams, listeners, providers etc. Furthermore, separating the layers would mean there is much more code to write, which also includes tons of boilerplate codes.

To mitigate the downsides, I decided to use cubits, which is a slight variation to BLoCs and it reduces the use of boilerplate codes significantly, improving the readability of the codebase.

SmartFM, the Smart Facility Management (FM) platform

To provide some context, my team’s objective here is to develop a FM platform that serves multiple tenants. Different tenants would have different configuration when it comes to their facilities. However, they all might want to share a common feature, for example, ad-hoc control over their lighting system.

At a developmental level, my mobile app design needs to provide an easy way to add new tenants (import their configuration) and remove tenants (remove their configuration) without affecting other tenants. In SWE terms, avoid falling into dependency hell.

As such, the idea of a base + tenant (multi-tenant) architecture was born.

What exactly is the Base?

The base is literally, as the name suggests, the base for the FM platform. This base is shared across all tenants which would standardize many of the shared features. The base provides a skeleton for the tenant to develop upon.

Dashboard page of the base

The main navigation is through the bottom navbar (navigation bar). The base provides a total of 5 main navigation items in the bottom navbar (from left to right):
- Dashboard
- Applications
- Alarms and Alerts
- Tasks
- Profile

The Tenants

You might have noticed that I placed emphasis on a feature-first development with a feature-focused architecture. The main reason is that in this base + tenant architecture, it is all about managing the features between the base and the tenants. This means that features need to be efficiently added/modified/removed to either base or tenant, depending on requirements.

On the tenant side, all the tenant required configuration can be set. For example, tenant-specific authentication can be configured. However, what if the tenant wants a custom feature specific only to that tenant?

The Missing Link between Base and Tenant

Now that we have the design for the base and the tenant, how do we link them? The naive approach is to manually link them via code by developing both in the same code base. However, this would clearly pose a ton of problems. The first glaring issue is that the build would be unnecessarily huge, containing codes of other tenants’ configurations that would never be used, which might even expose security vulnerabilities. The next glaring issue is maintainability and readability at scale. Imagine debugging through this codebase not knowing which tenant threw the error.

As such, I came up with the idea of developing the base as a flutter package which would be imported by the tenant. The tenant would then have access to base specific features through the package. But we still need to address our original question, tenant-specific custom features.

From the beginning, the base has always been the one to deliver the features to the UI. In other words, the base would have access to the features, and it is responsible for laying it out in the UI, be it in the bottom navbar or on specific page such as the dashboard. I decided to keep this design. So now the solution becomes straightforward. The tenant develops the custom features and passes it to the base. Since the base and tenants both follow a standard BLoC architecture, managing the features becomes a breeze.

To add another layer of abstraction and simplicity, each tenant is now able to have its own codebase, each importing the base package. As such, version control and version compatibility with the base can be easily managed.

Base + Tenant illustration

One great advantage to this idea is that each of this node can be independently worked on by different developers simultaneously with almost no conflicts.

The tricky problem — Navigation

Navigation becomes tricky because anyone who has experience with flutter’s bottom navigation bar would know that the bottom navbar is indexed based using a simple list. This is incompatible with flutter’s default Navigator which uses a Stack concept of LIFO (last-in first-out). You might ask why is this a problem, can they not simply exist independently within the app?

One of the main objectives of my navigation design is that the bottom navbar remains at its place even if the user navigates away to another page.

One thing we need to take note of is that a single feature can have multiple pages (routes) which might be developed using flutter’s default Navigator. When such a feature is passed into the base to be delivered onto one of the main pages, the navigation must work seamlessly between the bottom navbar and the flutter Navigator such that the bottom navbar stays at its place and maintains state while still using flutter’s Navigator to navigate to other pages. How do we solve this? Enter nested navigation.

Nested Navigation

We solve this issue by combining flutter’s default Navigator with the bottom navbar. In other words, we create a list of stacks (Navigators).

Visual Representation of Navigation Stacks

So, for each item in the bottom navbar, we assign a navigator, we call these main item navigators. Furthermore, if there are custom features, those custom features are also assigned a navigator, we call these custom feature navigators. So, by design, the custom feature navigators will be nested inside the main item navigators, seamlessly linking all features from the Root Navigator while keeping the bottom navbar in view. Seamless navigation achieved 👍

Putting It All into Action

So far, we have discussed about the design and theory. Does it actually work? Of course! I have built the entire architecture from ground-up and there are currently a few tenant FM apps that are being built using the base app. A fully fledged tenant app has already been deployed.

Conclusion

The design discussed in this article is just one possible implementation that I have explored. There could be various other ways of solving these problems. If you have an idea or a different way to solve these issues, I welcome you to explore the different ideas and experiment for yourself. Who knows, you could be building the next big thing. Cheers!

--

--

Riaz A

Systems Engineer @ GovTech | NUS Computer Engineering