KISS Architecture: A Strategy for Flutter Apps in Healthcare and Regulated Sectors

Summary

“KISS Architecture” explores the use of Flutter's inherent features to their utmost potential while curbing the use of third-party dependencies. By adopting this method, we achieve heightened software supply chain security, a crucial advantage for sectors such as healthcare, automotive, and finance. This approach simplifies developer onboarding, streamlines maintenance, and bolsters Flutter's robustness, making it an ideal choice for mission-critical, enterprise-grade applications demanding unwavering performance across various platforms.

Over the past several years, we've seen a steady increase in Flutter's adoption, particularly noticeable amongst startup MVPs and enterprise users for internal apps. Compared to other cross-platform solutions, the main advantage is that developers with native programming backgrounds find themselves comfortably at ease with Dart. Dart is a mature, multipurpose language that borrows elements from Kotlin, Swift, and C#. However, Flutter is more than just a language - it also encompasses an ecosystem of packages and best practices. These provide a significant productivity boost on one hand, while potentially overwhelming a beginner with the range of choices on the other.

Upon initiating a new Flutter project, several key decisions arise: determining which state management solution to utilize, how to handle routing, and the organization of dependency injection. Over my years of working with Flutter, I've devised an enterprise-friendly approach that I'm excited to share with you - I call it the "KISS Architecture", or "Keep It Simple, Stupid".

Exploring pubs.dev, you will find hundreds of packages for state management and routing. The popular ones include Riverpod, Provider, Bloc and GetX. Each package has its own unique strengths and weaknesses. Some borrow ideas from web SPA frameworks, while others take inspiration directly from the native landscape. One might easily get the impression that selecting a 3rd party state management or routing solution is necessary and that Flutter doesn’t offer anything out of the box. However, this is not the case since in practice Flutter has everything you need out of the box.

What conclusions can we draw from scrutinizing the popular state management and routing packages? Notably, all popular Flutter state management packages propose a way of injecting a state into a widget and updating the widget tree when the state alters. The distinctions lie in structuring the code and dependencies, single state versus multiple states, and being opinionated versus open-ended. Concurrently, the community seems to favour URL-based routing over an imperative approach. Some of the most popular packages for Flutter, although powerful, tend to be verbose.

Many of these techniques and packages can significantly enhance productivity in the early stages of a project. However, they can also introduce additional challenges in the long term. These packages often tie your project to 3rd parties, which may have unstable APIs, thereby increasing risks associated with long-term support. For instance, the popular Riverpod package recently underwent a significant API revision, now favouring code generation over the original approach. The GoRouter package, another favourite, is currently on major version 9. GoRouter significantly simplifies deep linking management, improves inversion of control and provides a method for persisting the state on page reload when used on the web. GoRouter, however, has seen breaking changes nearly every few months. Therefore, if you aim to stay abreast of the latest features, add-ons, and documentation, significant refactoring may be necessary. While the benefits may appear compelling, pose this question to yourself: Does your iOS or Android mobile app require deep linking, and if so, is it necessary for every route? More often than not, you need only a handful of links for things like opening notifications.

Right out of the box, Flutter is equipped with straightforward yet robust tools for both state management and routing. These features significantly minimize your reliance on third-party dependencies. Let's begin with state management. Flutter empowers you to create state objects that can trigger your widgets to rebuild with the latest state changes using ValueNotifiers and ValueListenableBuilders. To better understand this, let's examine the following example:

In this example, ValueNotifier creates a basic state object that stores a counter. The ValueListenableBuilder is a widget in Flutter that listens to the ValueListenable exposed by the ValueNotifier. ValueListenableBuilder automatically re-executes its builder function whenever the value it's listening to undergoes changes. The fusion of these two classes is sufficient to create an efficient state management system for your application.

However, suppose you need to disclose a more complex state. For this example, let's imagine we have a more complex state object named AppState, which includes user information and app settings. We'll create a ValueNotifier<AppState> object to store the global state. We will also create a helper controller class to mutate the global app state. Finally, we will enclose parts of the app in a ValueListenableBuilder to monitor state changes.

With relative simplicity, we've organized a powerful way for handling the global state within the application.

Now what about navigating between screens in your app? Flutter has a multitude of options on how you can organise navigation and routing. Even without using a package like GoRouter you can declaratively create named routes. I will argue, however, that for a vast majority of scenarios, you can get started with imperative navigation similar to what you can find in native apps on iOS and Android. All you need to do is to call push() and pop() methods:

Without resorting to 3rd party dependencies, you can easily pass values back and forth between routes. This method is easy to get started with and can always be replaced with a URL-based solution if the need arises. 

Now having established how to organise state management and routing, we need a dependency injection mechanism to pass the ValueListenable states to ValueListenableBuilder. The standard way of doing so in the flutter is to use InheritedWidget to inject the value down the widget tree. InheritedWidget as a dependency injection method however is rather verbose and may add unnecessary complexity. Fortunately, we can use a standard service locator pattern to access the value notifiers anywhere in the app. We can use the popular get_it package as it provides a simple service locator for Dart and Flutter with some additional tools for testing. Here is how you could use get_it to make the controller and the app state available anywhere in your app:

Alternatively, we can write our own lightweight implementation of this design pattern, avoiding the need for a 3rd party package while maintaining the same API:

What have we achieved with this approach to structuring a Flutter app? By refraining from immediately resorting to common, yet often verbose, third-party state management and navigation solutions, we reduce the number of dependencies of unknown provenance. This helps to enhance the supply chain security of our software. Given that Flutter itself is SLSA 3 compliant, we attain improved security, heightened reliability, and more straightforward compliance in sectors such as healthcare, automotive, and finance. At the same time, by maintaining a deliberately minimal design, we accelerate developer onboarding and streamline maintenance. This reinforces Flutter as an exemplary choice for mission-critical, enterprise-grade apps that demand dependable performance across all platforms.

KISS Architecture: A Strategy for Flutter Apps in Healthcare and Regulated Sectors

Summary

“KISS Architecture” explores the use of Flutter's inherent features to their utmost potential while curbing the use of third-party dependencies. By adopting this method, we achieve heightened software supply chain security, a crucial advantage for sectors such as healthcare, automotive, and finance. This approach simplifies developer onboarding, streamlines maintenance, and bolsters Flutter's robustness, making it an ideal choice for mission-critical, enterprise-grade applications demanding unwavering performance across various platforms.

Over the past several years, we've seen a steady increase in Flutter's adoption, particularly noticeable amongst startup MVPs and enterprise users for internal apps. Compared to other cross-platform solutions, the main advantage is that developers with native programming backgrounds find themselves comfortably at ease with Dart. Dart is a mature, multipurpose language that borrows elements from Kotlin, Swift, and C#. However, Flutter is more than just a language - it also encompasses an ecosystem of packages and best practices. These provide a significant productivity boost on one hand, while potentially overwhelming a beginner with the range of choices on the other.

Upon initiating a new Flutter project, several key decisions arise: determining which state management solution to utilize, how to handle routing, and the organization of dependency injection. Over my years of working with Flutter, I've devised an enterprise-friendly approach that I'm excited to share with you - I call it the "KISS Architecture", or "Keep It Simple, Stupid".

Exploring pubs.dev, you will find hundreds of packages for state management and routing. The popular ones include Riverpod, Provider, Bloc and GetX. Each package has its own unique strengths and weaknesses. Some borrow ideas from web SPA frameworks, while others take inspiration directly from the native landscape. One might easily get the impression that selecting a 3rd party state management or routing solution is necessary and that Flutter doesn’t offer anything out of the box. However, this is not the case since in practice Flutter has everything you need out of the box.

What conclusions can we draw from scrutinizing the popular state management and routing packages? Notably, all popular Flutter state management packages propose a way of injecting a state into a widget and updating the widget tree when the state alters. The distinctions lie in structuring the code and dependencies, single state versus multiple states, and being opinionated versus open-ended. Concurrently, the community seems to favour URL-based routing over an imperative approach. Some of the most popular packages for Flutter, although powerful, tend to be verbose.

Many of these techniques and packages can significantly enhance productivity in the early stages of a project. However, they can also introduce additional challenges in the long term. These packages often tie your project to 3rd parties, which may have unstable APIs, thereby increasing risks associated with long-term support. For instance, the popular Riverpod package recently underwent a significant API revision, now favouring code generation over the original approach. The GoRouter package, another favourite, is currently on major version 9. GoRouter significantly simplifies deep linking management, improves inversion of control and provides a method for persisting the state on page reload when used on the web. GoRouter, however, has seen breaking changes nearly every few months. Therefore, if you aim to stay abreast of the latest features, add-ons, and documentation, significant refactoring may be necessary. While the benefits may appear compelling, pose this question to yourself: Does your iOS or Android mobile app require deep linking, and if so, is it necessary for every route? More often than not, you need only a handful of links for things like opening notifications.

Right out of the box, Flutter is equipped with straightforward yet robust tools for both state management and routing. These features significantly minimize your reliance on third-party dependencies. Let's begin with state management. Flutter empowers you to create state objects that can trigger your widgets to rebuild with the latest state changes using ValueNotifiers and ValueListenableBuilders. To better understand this, let's examine the following example:

In this example, ValueNotifier creates a basic state object that stores a counter. The ValueListenableBuilder is a widget in Flutter that listens to the ValueListenable exposed by the ValueNotifier. ValueListenableBuilder automatically re-executes its builder function whenever the value it's listening to undergoes changes. The fusion of these two classes is sufficient to create an efficient state management system for your application.

However, suppose you need to disclose a more complex state. For this example, let's imagine we have a more complex state object named AppState, which includes user information and app settings. We'll create a ValueNotifier<AppState> object to store the global state. We will also create a helper controller class to mutate the global app state. Finally, we will enclose parts of the app in a ValueListenableBuilder to monitor state changes.

With relative simplicity, we've organized a powerful way for handling the global state within the application.

Now what about navigating between screens in your app? Flutter has a multitude of options on how you can organise navigation and routing. Even without using a package like GoRouter you can declaratively create named routes. I will argue, however, that for a vast majority of scenarios, you can get started with imperative navigation similar to what you can find in native apps on iOS and Android. All you need to do is to call push() and pop() methods:

Without resorting to 3rd party dependencies, you can easily pass values back and forth between routes. This method is easy to get started with and can always be replaced with a URL-based solution if the need arises. 

Now having established how to organise state management and routing, we need a dependency injection mechanism to pass the ValueListenable states to ValueListenableBuilder. The standard way of doing so in the flutter is to use InheritedWidget to inject the value down the widget tree. InheritedWidget as a dependency injection method however is rather verbose and may add unnecessary complexity. Fortunately, we can use a standard service locator pattern to access the value notifiers anywhere in the app. We can use the popular get_it package as it provides a simple service locator for Dart and Flutter with some additional tools for testing. Here is how you could use get_it to make the controller and the app state available anywhere in your app:

Alternatively, we can write our own lightweight implementation of this design pattern, avoiding the need for a 3rd party package while maintaining the same API:

What have we achieved with this approach to structuring a Flutter app? By refraining from immediately resorting to common, yet often verbose, third-party state management and navigation solutions, we reduce the number of dependencies of unknown provenance. This helps to enhance the supply chain security of our software. Given that Flutter itself is SLSA 3 compliant, we attain improved security, heightened reliability, and more straightforward compliance in sectors such as healthcare, automotive, and finance. At the same time, by maintaining a deliberately minimal design, we accelerate developer onboarding and streamline maintenance. This reinforces Flutter as an exemplary choice for mission-critical, enterprise-grade apps that demand dependable performance across all platforms.

KISS Architecture: A Strategy for Flutter Apps in Healthcare and Regulated Sectors

Summary

“KISS Architecture” explores the use of Flutter's inherent features to their utmost potential while curbing the use of third-party dependencies. By adopting this method, we achieve heightened software supply chain security, a crucial advantage for sectors such as healthcare, automotive, and finance. This approach simplifies developer onboarding, streamlines maintenance, and bolsters Flutter's robustness, making it an ideal choice for mission-critical, enterprise-grade applications demanding unwavering performance across various platforms.

Over the past several years, we've seen a steady increase in Flutter's adoption, particularly noticeable amongst startup MVPs and enterprise users for internal apps. Compared to other cross-platform solutions, the main advantage is that developers with native programming backgrounds find themselves comfortably at ease with Dart. Dart is a mature, multipurpose language that borrows elements from Kotlin, Swift, and C#. However, Flutter is more than just a language - it also encompasses an ecosystem of packages and best practices. These provide a significant productivity boost on one hand, while potentially overwhelming a beginner with the range of choices on the other.

Upon initiating a new Flutter project, several key decisions arise: determining which state management solution to utilize, how to handle routing, and the organization of dependency injection. Over my years of working with Flutter, I've devised an enterprise-friendly approach that I'm excited to share with you - I call it the "KISS Architecture", or "Keep It Simple, Stupid".

Exploring pubs.dev, you will find hundreds of packages for state management and routing. The popular ones include Riverpod, Provider, Bloc and GetX. Each package has its own unique strengths and weaknesses. Some borrow ideas from web SPA frameworks, while others take inspiration directly from the native landscape. One might easily get the impression that selecting a 3rd party state management or routing solution is necessary and that Flutter doesn’t offer anything out of the box. However, this is not the case since in practice Flutter has everything you need out of the box.

What conclusions can we draw from scrutinizing the popular state management and routing packages? Notably, all popular Flutter state management packages propose a way of injecting a state into a widget and updating the widget tree when the state alters. The distinctions lie in structuring the code and dependencies, single state versus multiple states, and being opinionated versus open-ended. Concurrently, the community seems to favour URL-based routing over an imperative approach. Some of the most popular packages for Flutter, although powerful, tend to be verbose.

Many of these techniques and packages can significantly enhance productivity in the early stages of a project. However, they can also introduce additional challenges in the long term. These packages often tie your project to 3rd parties, which may have unstable APIs, thereby increasing risks associated with long-term support. For instance, the popular Riverpod package recently underwent a significant API revision, now favouring code generation over the original approach. The GoRouter package, another favourite, is currently on major version 9. GoRouter significantly simplifies deep linking management, improves inversion of control and provides a method for persisting the state on page reload when used on the web. GoRouter, however, has seen breaking changes nearly every few months. Therefore, if you aim to stay abreast of the latest features, add-ons, and documentation, significant refactoring may be necessary. While the benefits may appear compelling, pose this question to yourself: Does your iOS or Android mobile app require deep linking, and if so, is it necessary for every route? More often than not, you need only a handful of links for things like opening notifications.

Right out of the box, Flutter is equipped with straightforward yet robust tools for both state management and routing. These features significantly minimize your reliance on third-party dependencies. Let's begin with state management. Flutter empowers you to create state objects that can trigger your widgets to rebuild with the latest state changes using ValueNotifiers and ValueListenableBuilders. To better understand this, let's examine the following example:

In this example, ValueNotifier creates a basic state object that stores a counter. The ValueListenableBuilder is a widget in Flutter that listens to the ValueListenable exposed by the ValueNotifier. ValueListenableBuilder automatically re-executes its builder function whenever the value it's listening to undergoes changes. The fusion of these two classes is sufficient to create an efficient state management system for your application.

However, suppose you need to disclose a more complex state. For this example, let's imagine we have a more complex state object named AppState, which includes user information and app settings. We'll create a ValueNotifier<AppState> object to store the global state. We will also create a helper controller class to mutate the global app state. Finally, we will enclose parts of the app in a ValueListenableBuilder to monitor state changes.

With relative simplicity, we've organized a powerful way for handling the global state within the application.

Now what about navigating between screens in your app? Flutter has a multitude of options on how you can organise navigation and routing. Even without using a package like GoRouter you can declaratively create named routes. I will argue, however, that for a vast majority of scenarios, you can get started with imperative navigation similar to what you can find in native apps on iOS and Android. All you need to do is to call push() and pop() methods:

Without resorting to 3rd party dependencies, you can easily pass values back and forth between routes. This method is easy to get started with and can always be replaced with a URL-based solution if the need arises. 

Now having established how to organise state management and routing, we need a dependency injection mechanism to pass the ValueListenable states to ValueListenableBuilder. The standard way of doing so in the flutter is to use InheritedWidget to inject the value down the widget tree. InheritedWidget as a dependency injection method however is rather verbose and may add unnecessary complexity. Fortunately, we can use a standard service locator pattern to access the value notifiers anywhere in the app. We can use the popular get_it package as it provides a simple service locator for Dart and Flutter with some additional tools for testing. Here is how you could use get_it to make the controller and the app state available anywhere in your app:

Alternatively, we can write our own lightweight implementation of this design pattern, avoiding the need for a 3rd party package while maintaining the same API:

What have we achieved with this approach to structuring a Flutter app? By refraining from immediately resorting to common, yet often verbose, third-party state management and navigation solutions, we reduce the number of dependencies of unknown provenance. This helps to enhance the supply chain security of our software. Given that Flutter itself is SLSA 3 compliant, we attain improved security, heightened reliability, and more straightforward compliance in sectors such as healthcare, automotive, and finance. At the same time, by maintaining a deliberately minimal design, we accelerate developer onboarding and streamline maintenance. This reinforces Flutter as an exemplary choice for mission-critical, enterprise-grade apps that demand dependable performance across all platforms.

About me

Elliot Tikhomirov

Based in Perth, Western Australia, I am a senior software developer passionate about creating impactful mobile and web apps with Flutter and React Native. Beyond my technical prowess, I have extensive experience in leading development teams and crafting robust enterprise applications. My keen attention to detail and problem-solving skills enable me to innovate with AI-powered features for modern devices and tackle even the most complex business challenges.

About me

Elliot Tikhomirov

Based in Perth, Western Australia, I am a senior software developer passionate about creating impactful mobile and web apps with Flutter and React Native. Beyond my technical prowess, I have extensive experience in leading development teams and crafting robust enterprise applications. My keen attention to detail and problem-solving skills enable me to innovate with AI-powered features for modern devices and tackle even the most complex business challenges.

About me

Elliot Tikhomirov

Based in Perth, Western Australia, I am a senior software developer passionate about creating impactful mobile and web apps with Flutter and React Native. Beyond my technical prowess, I have extensive experience in leading development teams and crafting robust enterprise applications. My keen attention to detail and problem-solving skills enable me to innovate with AI-powered features for modern devices and tackle even the most complex business challenges.

© 2024 Elliot Tikhomirov

© 2024 Elliot Tikhomirov

© 2024 Elliot Tikhomirov