When unit testing in dart, the basic solution for checking that a variable has the expected value is to use the flutter_test expect
function, which takes a variable and a matcher. The main problem with this function is the dynamic typing of its arguments. If the type of the variable does not match the type of the matcher, an error is raised only during test execution, without any indication to the developer during test writing.
The checks package exposes several methods for checking the value and type of variables in the equivalent function and provides support for deep equality of collections. The available methods are automatically filtered by the type of variable in the function. In addition, Checks enhances the development experience by providing filtered autocompletion directly on the provided methods in modern IDEs. All check methods can be called in a chain, a declarative approach that promotes code clarity and readability. The library also provides the ability to expect Futures
or Streams
values. Finally, the ability to customize tests removes limitations by allowing you to combine existing tests or create logic entirely specific to your context.
Although little known, the checks
library offers significant benefits that make testing more explicit and readable. That's why we make it our default solution for Flutter projects and strongly recommend its adoption.
In Flutter, global state management is achieved through InheritedWidget
and ChangeNotifier
, but these APIs have several shortcomings, including verbosity, complexity, difficulty in testing and the impossibility of creating multiple states of the same type.
Riverpod is a reactive caching library that solves these problems. Inspired by react-query
, it proposes to serve data via providers, which are declared outside the widget lifecycle and can automatically rebuild widgets that listen to them. Providers can serve asynchronous data coming, for example, from a call API or a local database, handle errors and caching, and easily define other functionalities such as debounce or pull-to-refresh.
Riverpod is also compilesafe, offers a declarative API, is actively maintained, and is supported by a vast community (6k stars on github, 98% popularity on pub. dev). Since last year, we've been able to experiment with its code generation tools, which enable hot reloads to update a provider and further reduce API verbosity and complexity.
We recommend using Riverpod, which we've been using for 3 years on Flutter projects of all sizes. The announcement of riverpod 3, which should make it possible to define and reuse providers with generic types as parameters, only reinforces our enthusiasm.
In Flutter, slivers are a type of widget that integrate into scrollable views and react to scrolling to create complex, animated scrollable screens. While slivers are not particularly difficult to use, they are low-level widgets and their writing is complex.
While the framework offers several very high-level slivers, such as the SliverAppBar
and the SliverList
, it can be difficult to customize scrollable views beyond the classic ones on offer. The sliver_tools
library offers a collection of ready-to-use slivers that enhance the framework's native slivers, providing an intermediate level of flexibility between high-level widgets and low-level RenderObjects
.
Among the most frequently used are MultiSliver
, which lets you combine several slivers into one to improve code quality by cutting out responsibilities, and SliverPinnedHeader
, which lets you create scrollable elements that snap to the top of the scrollable view to keep them visible and give a pleasant navigation effect.
Our use of sliver_tools
on projects has been very conclusive, with no reported limitations. This toolbox is now part of our standard stack at Theodo, enabling us to bring to life the original scrollable views imagined in collaboration with our designers.
Mapping in mobile applications is a complex subject due to the performance and limitations imposed by certain libraries for displaying various elements. Traditionally, developers have turned to solutions such as Mapbox and Google Maps, which use a C++ graphics engine to render maps. While these solutions are robust, they don't always integrate seamlessly with Flutter, particularly when it comes to customizing the elements to be displayed on the map.
The flutter_map
library has been written entirely in Dart, with a declarative and composable API for UI elements, combined with an imperative approach for manual control (e.g. animations). This approach enables developers to take full advantage of Flutter's benefits, notably by easily integrating widgets on the map. It also benefits from a varied ecosystem of open-source extensions. We've used flutter_map
to create highly customized raster maps and have been impressed by its ease of use and performance for raster maps.
flutter_map
does, however, have some important limitations. The extension for vector maps suffers from major performance problems, making it impractical for this type of map. Transitions between zoom levels also lack fluidity (compared with the Mapbox or Google Maps SDKs). This limitation has prompted us to opt for alternative solutions for projects requiring vector maps. To solve these problems, the community is actively working on a solution using Flutter's latest advances, such as Impeller or flutter_gpu
.
We recommend that you try flutter_map
for integrating raster maps into your Flutter projects, due to its ease of use and performance. However, we invite you to remain open to other available solutions, especially when using vector maps.
There are several local database solutions available in Flutter, including SharedPreferences, Isar, Hive, ObjectBox, SQLite and others. Each of these solutions has its own advantages and disadvantages in terms of performance, ease of use and functionality. In this context, we have integrated MMKV for Flutter on one of our projects. MMKV is a high-performance, easy-to-use key/value storage library developed by Tencent. Used in the WeChat application, MMKV is designed to offer optimum performance using mmap
and protobuf
, enabling memory to be synchronized with files and values to be encoded/decoded efficiently.
By using Dart FFI for synchronous read and write operations, we can take full advantage of this Flutter performance. We have measured that opening an MMKV database is much more CPU-efficient than other solutions, such as Isar, which can block a thread for several hundred milliseconds. MMKV's API is clear and minimalist, making it easy to integrate and use in Flutter projects. Since the migration to a federated plugin architecture, the development experience with MMKV has improved significantly. In addition, MMKV supports data encryption, providing an extra layer of security.
However, MMKV for Flutter does have a few limitations. Currently, only iOS and Android platforms are supported, although MMKV itself is available on iOS, Android, Linux, macOS and Windows. Future web support could prove complex to implement. In addition, a recent minor update removed support for Android ARM7 and x86 architectures, impacting around 2% of our production users.
Despite these challenges, we find MMKV to be a very interesting solution. It's simple and extremely powerful, developed by Tencent, which makes it highly reliable and easy to maintain over the long term. Although very popular in the React Native and Native communities, MMKV is still relatively unknown in the Flutter ecosystem, probably due to the presence of already established synchronous local databases.
We recommend testing MMKV in your Flutter projects. However, a thorough assessment of the platforms and devices used by your users is essential to ensure successful integration and compatibility with your project's specific requirements.
Forms management and user input validation are crucial but complex aspects of application development. Reactive Forms, a library for Flutter, offers model-based form management inspired by Angular. At Theodo, we use it for projects requiring forms, such as login, registration or payment forms.
Reactive Forms boasts a rich ecosystem of predefined, asynchronous and customizable validators. This flexibility makes it easy to manage complex, project- specific validation rules. However, it can be difficult to get to grips with, and the code required to define a form is sometimes verbose. The typing system could also be improved. An extension using code generation is currently under development to simplify this functionality.
Reactive Forms integrates well with state management tools such as Riverpod or BLoC, enabling efficient reaction to form state changes. It also enables unit testing of each validation rule, ensuring that no regression is introduced during updates. This improves application productivity and maintainability.
Reactive Forms' documentation and community support are excellent, with an active community and regular updates. The package has 458 stars on GitHub and 839 likes on Pub, and the ecosystem of community-created validators is a major asset.
We recommend trying out Reactive Forms for your Flutter projects because of its robust validation capabilities, flexibility and seamless integration with state management tools. However, given the initial complexity and verbosity of the code, you should proceed with caution.
Update times are a major challenge in mobile development, as each version must be validated by the stores, delaying the availability of urgent patches to users. What's more, you must wait for each user to update the application. Shorebird, announced in early 2024 by Flutter creator Eric Seidel, is an open-source solution for deploying Flutter application updates over-the-air (OTA) without going through the stores. This approach enables minor updates to be deployed directly via their servers, accelerating the development and deployment cycle.
Integrating Shorebird into a Flutter project is straightforward but has its drawbacks. Cost can be a barrier for some teams, and the shorebird patch command is slower than flutter build, which can slow down the update process, especially in a QA environment that's active several times a day. Currently, Shorebird only supports iOS and Android, excluding desktop applications. In addition, the application has to be restarted to run the updated code, which can affect the user experience.
Despite these drawbacks, Shorebird offers a valuable feature as the only OTA update solution for Flutter. This tool meets a crucial need in mobile development, and respects store rules by updating only interpreted code.
Although Shorebird's CLI has recently reached version 1, it still presents a few instabilities, but these are quickly resolved as the development team is attentive to feedback from the community.
Shorebird shows promising results and has the potential to transform release management in Flutter applications. We recommend testing this technology to assess its impact on your development and deployment cycles.
During development, we rely on code generation tools such as freezed
, json_serializable
and build_runner
. These tools, while essential for addressing recurring issues such as deserialization, immutability or deep equality, introduce inefficiencies and detract from the developer experience by imposing frequent manual code generation. They also tend to clutter up our projects with generated files.
Dart macros, currently in beta, promise to transform this dynamic by integrating metaprogramming directly into the compiler. This allows developers to generate code as they write. They thus improve productivity by reducing repetitive generations and maintaining a cleaner code base.
Macros, however, could compromise Dart's simplicity and readability, which are essential to its ease of learning. Dart is renowned for being a "boring" language in the good sense of the word: predictable and stable. The introduction of macros, by contrast, introduces a dimension of complexity not traditionally associated with Dart.
If macros seem to threaten this transparency, the proposed augmentation system allows developers to visualize augmented code with a single click in their development environment, thus maintaining a clear understanding of the code being executed.
Note that macros will be used mainly by library maintainers, such as freezed
or json_serializable
, and not by all Flutter developers on a day-to-day basis. We'll be keeping a close eye on how maintainers and the community welcome and make use of this new feature.
We're optimistic about the potential of Dart macros to simplify and improve code generation in our Flutter applications. Although the macros are currently in the experimental phase, the Dart team's roadmap calls for a stable version in early 2025.
At Theodo, we believe that automated testing is one of the best ways to prevent functional or visual regressions in mobile applications. However, existing Flutter testing solutions, such as unit tests and widget tests, do not always comprehensively cover endto-end (E2E) scenarios.
Patrol is a Flutter library that simplifies the writing and execution of E2E tests. It integrates an API that makes it very easy to interact with the test device's native functionalities, such as permissions, notifications or parameters. The patrol_finders library, which is now independent of Patrol, also offers syntactic sugar, making test writing more intuitive and less error prone.
At Theodo, we approach automated testing by using unit tests instead, as well as adaptive UI tests (also known as golden tests) thanks to tools like adaptive_test. They guarantee complete functional coverage while visually verifying the user interface.
For E2E testing, Patrol offers an extensive API but will only work with Flutter applications. For this need, our current choice is Maestro, a proven technology whose learning we can share with the React Native and native iOS/Android teams. However, we find that Patrol solves the problem of E2E testing in Flutter in an interesting and intuitive way.
For Flutter developers looking to improve their E2E testing, Patrol is worth exploring, especially if you want to use a tool that integrates seamlessly with your existing Flutter tests. Although our current choice is Maestro, we'll continue to follow Patrol's development.
Managing the overall state of an application is critical to its maintainability and performance. Several solutions have been proposed for Flutter, and one new option deserves our attention: Signals.
Signals is an innovative library for Flutter that simplifies state management with reactive signals. Inspired by reactivity concepts from the JavaScript ecosystem, such as preact, it provides fine-grained reactivity, where each signal represents a value encapsulated in a reactive shell. Signals can be simple states or computations derived from other states, forming an acyclic graph of dependencies.
Signals offers several key advantages:
The Signals API is like that of Riverpod, but with a more advanced push-pull system. Unlike Riverpod, which also handles reactive dependency injection, Signals focuses exclusively on state management.
To scale a Flutter application, you need to use a dependency injection solution such as InheritedWidget, Provider or GetIt.
At Theodo, we're exploring the potential benefits of Signals, a promising technology that could unify state management practices in the currently fragmented Flutter community. Although Signals is still relatively new and not widely used in largescale production projects, its growing popularity in other web frameworks indicates a convergence towards a standard reactive programming technique.
We encourage you to experiment with Signals, bearing in mind that it hasn't yet reached the level of adoption of established solutions like Riverpod or BLoC. Keep an eye on this package for future developments and integration potential.
Local data management is especially important for applications that need to operate offline or that require a high level of confidentiality and security. An efficient database ensures optimal data management, fast access, and a smooth user experience.
Isar is a fast, easy-to-use NoSQL database library designed specifically for Flutter applications. Designed to replace Hive, a key/value database widely used in the Flutter ecosystem, Isar promises high performance thanks to its engine written in Rust. It offers advanced features such as composite indexes, asynchronous operations, and crossplatform support (iOS, Android, desktop). However, we have identified a few issues that warrant a cautious approach.
The last period of significant activity on Isar's GitHub repository was a year ago, raising concerns about its development and maintenance. In addition, internal testing conducted as part of our Kaizen initiative has shown that the time required to open encrypted data with Isar can slow the opening of an application by several hundred milliseconds. This limitation, which occurs at a critical moment in the user experience, is not present in other more mature solutions such as MMKV. Therefore, despite the promise of high performance guaranteed by its Rust engine, Isar has speed and reliability limitations that currently block a large-scale production application.
We recommend choosing solutions other than Isar to manage local data storage, such as MMKV or ObjectBox. Although the Isar library is promising, it's best to wait until it reaches a higher level of maturity and stability before using it for production projects.
Find out what our experts have to say about the techniques, platforms, tools, languages and frameworks associated with the main mobile technologies we use every day at BAM: React Native, Flutter and Native.