Avoid These Libraries in Dart
When developing with Dart and Flutter, choosing the right packages can make your project more productive and functional. But some of them can cause problems, like poor performance, bad code, or maintenance issues.
Outline
- Architecture & State Management
- Dependencies
- Storage
- Flutter Utils
Architecture & State Management
Libraries that impose rules on the look and feel of the architecture, project structure, state transitions, and data flow.
Get
The most popular package on pub.dev and the most criticized by the community is Get. This package attracts beginners with its seemingly "simple" interface and a variety of tools readily available. It claims to handle many tasks such as navigation, state management, storage, and localization, but it doesn't excel in any of these areas.
Beneath the surface, it contains poorly written code and numerous issues. Get promotes flawed architectural practices, turning your project into a chaotic mess. This often leads to fragile, unsupportable applications that are difficult to maintain, where adding new features in one part can cause other parts to break.
Additionally, its ecosystem, including get_storage, cli, and server, is also not recommended.
Riverpod
This framework for data binding and state management handles the lifecycle of data, caching, rebuilding, side effects, and dependencies, which may seem overwhelming at first.
It relies on global variables (providers) to retrieve and cache data. All data is stored in a central container, typically located at the root of the application. By taking on too much responsibility and promoting extensive use of global variables, this library leads to side effects that cause unpredictable rebuilds, recomputations, and tightly coupled, tangled code.
If you want to learn more, there is an article that explains the problems in detail:
Stacked
Stacked is a framework that provides an architecture for Flutter applications based on the MVVM (Model - View - View Model) pattern.
It integrates other packages from pub.dev, notably using both get_it and provider to manage and initialize dependencies. This approach is problematic because it creates a risky and unusual dependency flow, where dependencies are stored in a service locator and simultaneously provided to the Flutter tree via inherited widgets. Using service locators is generally discouraged.
Moreover, Stacked promotes flawed techniques, such as failing to respect the separation of concerns. Each layer should handle its own responsibilities, but Stacked’s approach blurs these boundaries, leading to poorly organized code.
MobX
This state management library uses observables, actions, and reactions to handle state changes. The underlying paradigm, TRP (Transparent Reactive Programming), encourages automatic subscriptions. This means that when data changes, related "computed" variables are automatically updated. However, these side effects can lead to unnecessary rebuilds and unpredictable behavior. In addition, creating robust business logic with this library is quite challenging.
Hydrated Bloc
Hydrated bloc is an extension for the bloc package that used to store bloc states and restore them automatically when the application starts.
This idea is inherently wrong because there is no need to store states. States can be different. For example, if the user closes the application when some bloc failed to load, do we need to recover that failed state? Obviously not.
Instead, it is needed to store data - so that when bloc is instantiated, it is possible to emit the correct state.
Dependencies
Libraries that manage the dependencies initialization & flow in the application.
Get_it
Get_it is a popular library for managing dependency initialization. However, it follows the service locator anti-pattern, resulting in tightly coupled code that is difficult to test and maintain. Dependencies are resolved at runtime, so if you forget to register one or accidentally delete it, you may face additional development delays or discover issues through your production users.
Instead, consider initializing your application's dependencies (such as storage, repositories, and cloud services) in the Composition Root and organizing them into containers.
Storage
Libraries used to store the data locally on the device.
Hive
A widely used key-value database that enables storing various types of objects on the device. However, it has numerous disadvantages that make this library almost unusable for production apps. Here are a few most important:
Lacks essential database features: Hive does not support relations, foreign keys, indexes, joins, triggers, or migrations.
- No indexes: Any non-key-based queries require full database scans.
- No migrations: Schema changes necessitate clearing all saved data.
Hive performs poorly compared to SQLite, despite claims of superior performance. Hive stores all data in memory, leading to lags, stutters, and potential crashes with large datasets.
Hive is an extremely unstable database. It lacks a failure recovery mechanism, making it susceptible to corruption and data loss. Commonly, projects implement a function to open the database and, upon encountering an issue, delete the database and create a new one—a drastic measure, indeed. Additionally, accessing a Hive box from another isolate corrupts the database, resulting in complete data loss.
Additionally, Hive isn't a database standard, making experience with it non-transferable and inapplicable beyond specific projects. Moreover, Hive is no longer supported. These issues make Hive a poor choice. Instead, consider using shared_preferences for key-value storage and SQLite for structured storage.
Isar
Isar is developed by the same author as Hive and is a continuation of his work. It now uses a library called libmdbx via Rust bindings. However, since libmdbx is written in C, there’s no need to communicate from Dart to Rust and then to C; it can directly use Dart to C communication.
Apart from implementation details, the Isar database (a wrapper around libmdbx) is not a reliable choice and shares many disadvantages with Hive. For instance, the database can become corrupt with no recovery options available. Furthermore, Isar is significantly slower than SQLite and has not been updated for > 1 year.
Flutter Utils
Utility libraries that tend to "simplify" Flutter development, but actually make it more difficult.
Flutter Hooks
Flutter Hooks is a utility library that provides shortcut functions, such as "useAnimationController" and "useTextEditingController," for widgets, as shown below:
class Example extends HookWidget {
const Example({Key? key, required this.duration})
: super(key: key);
final Duration duration;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: duration);
return Container();
}
}
This might seem convenient initially, but it imposes limitations on how code is written. This package obscures the logic, making things unclear. Additionally, there are many considerations for these functions to work properly.
For more details, check out the GitHub issue where Flutter SDK maintainers explain why this approach is fundamentally flawed:
Easy Localization
Easy_localization is a library for handling localization. Instead of generating native Dart localizations using intl, it parses JSON files at runtime to obtain translations. This approach involves extensive logic, degrades performance, and is not safe.
For better performance and more features, consider using intl and the Flutter SDK to generate locales. Additionally, these tools have a larger community for support.
Flutter Screenutil
Flutter ScreenUtil adjusts all insets based on screen size, intending to make the application look similar across devices. However, this approach often results in inconvenient applications, as users with larger devices see excessively large views instead of more information. Implementing an adaptive UI is a better solution.
Mix
A recent package for building user interfaces, it serves as a replacement for the Material library and offers an API somewhat similar to CSS.
final style = Style(
$box.height(100),
$box.width(100),
$box.margin(10, 20),
$box.color.blue(),
$box.borderRadius(10),
$box.border(
color: Colors.black,
width: 1,
style: BorderStyle.solid,
),
);
Box(
style: style,
child: Text('Hello Mix'),
);
This approach falls short in many ways. It is neither easier nor more attractive. The main issue is that it deviates from the Flutter way and feels out of place within the ecosystem. You’ll spend unnecessary time teaching your developers to use this library and experience significant delays when issues arise.
The Material offers a solid foundation for building custom UI kits, supporting semantics, focus, and more. You can create fully customized buttons using StyledButton or even fork it for complete customization.