Migrating from GetX: Boosting Flutter App Development Efficiency — Part 1 : Architecture and Folder Structure

Fajrian Aidil Pratama
12 min readMay 20, 2023

--

Introduction

In the world of Flutter app development, staying up to date with the latest packages and tools is essential to ensure optimal performance and a seamless user experience. As a long-time GetX user, I have greatly benefited from its power in accelerating Flutter app development. Its simplicity, reactivity, and intuitive state management capabilities have significantly reduced development time for my freelance projects. However, as time passed, I began to notice a lack of updates and concerns about its long-term maintenance.

Determined to enhance my Flutter project and unlock new possibilities, I embarked on a journey to migrate from the GetX package to a new set of packages that promised even better results. In this article, I will share my experience, the packages I adopted, and the significant improvements I witnessed along the way. By exploring alternative packages and leveraging their unique features, I aimed to boost performance, streamline workflows, and future-proof my app development process.

Join me as I delve into the world of package migration, uncovering the advantages of the packages I chose, and showcasing the tangible benefits they brought to my projects. From state management to routing, dependency injection, and localization, I will highlight the tools that have revolutionized my Flutter app development experience. Let’s embark on this exciting journey of optimization and discover how these new packages have transformed my app development workflow for the better.

Unleashing the Power: Replacing GetX with Robust Packages for Enhanced Flutter App Development

During my quest for suitable replacements, I explored a range of packages that boasted robust features and active development communities. After thorough evaluation, I made strategic decisions on the packages that best aligned with my project’s requirements. Let’s dive into each package and its corresponding use case, showcasing how they seamlessly replaced GetX and elevated my Flutter app development process.

  1. BLoC for State Management: To tackle state management, I adopted the BLoC (Business Logic Component) pattern. With BLoC, I achieved separation of concerns, allowing me to isolate business logic from UI components. By decoupling these elements, I improved code maintainability and testability. BLoC’s stream-based approach facilitated seamless communication between components, ensuring efficient state updates and a responsive user interface.
  2. auto_route for Routing: For efficient navigation within the app, I turned to Auto Route. This package simplified route management and eliminated the need for manual route declaration. By leveraging code generation, Auto Route generated type-safe route navigation code, reducing the likelihood of errors and improving overall development speed. With AutoRoute’s declarative syntax, I effortlessly defined routes and passed parameters, enhancing the app’s navigation flow.
  3. get_it and injectable for Dependency Injection: To achieve clean and modular code, I integrated get_it and injectable for dependency injection. These packages enabled me to decouple dependencies and promote code reusability. By defining dependencies as injectable objects, I ensured loose coupling between components and facilitated easy swapping of implementations. get_it provided a straightforward service locator pattern, while injectable enhanced code generation capabilities, simplifying the setup process.
  4. slang for Localization: To deliver a multilingual experience, I adopted slang for localization. This package streamlined the process of translating the app’s text and user interface elements. With Slang, I defined language-specific resource files, allowing for effortless switching between languages at runtime. The dynamic nature of slang ensured that language changes were reflected immediately, enhancing user engagement and accessibility.

In addition to adopting these packages, I also embraced a clean architecture approach during the migration process. Clean architecture promotes separation of concerns and encapsulation of business logic, making the codebase more maintainable and scalable. With a clear separation between presentation, domain, and data layers, I achieved improved code organization and enhanced testability.

By combining these powerful packages and embracing clean architecture principles, I successfully replaced GetX in my Flutter app. The migration not only resolved the concerns I had about maintenance and updates but also introduced significant improvements in terms of code organization, scalability, and overall development efficiency.

In the next part of this series, I will delve deeper into each package, providing code examples and real-world use cases to illustrate their effectiveness in replacing GetX. Stay tuned as we explore the remarkable capabilities of these packages and how they contributed to the success of my Flutter app development journey.

Architecture and Folder Structure Overview: Embracing Clean Architecture

As part of my migration process, I decided to revamp the architecture and folder structure of my Flutter app to adhere to the principles of Clean Architecture. This architectural approach emphasizes separation of concerns, maintainability, and testability, allowing for better scalability and code organization.

Understanding the Old Folder Structure

Previously, my codebase followed a folder structure generated using the get init command, which served its purpose initially. However, as the project grew, I noticed that it became challenging to maintain and scale the codebase effectively. The old structure consisted of folders like base, config, core, modules, routes, shared, and utils. While this structure reflected the different aspects of the app, it lacked a clear separation of concerns and a modular approach.

The old folder structure follows a modular approach, where each feature is created as a module within the lib/app/modules folder. Let's take a closer look at the structure of each feature module:

  • Binding: Each feature module has its own subfolder named bindings, which handles the dependency injection for that specific feature. This is where you would use Get.put or Get.lazyPut to register the necessary dependencies for the feature.
  • Controller: The controller subfolder within each feature module contains the logic and state management for that particular feature. You would typically find a controller file that extends GetxController or GetXController to handle the business logic.
  • View: The view subfolder within each feature module houses the UI components related to the feature. The view files usually extend GetView to integrate with the GetX framework and provide access to controller functionalities.

To facilitate navigation between features, the modules are added to the app routes. This is done in the routes folder, specifically in the app_pages.dart file. Each feature module is registered with its respective route path using GetX's routing system.

To navigate to a specific feature, you can use Get.to followed by the route path. In this case, the route paths are defined in the app_routes.dart file, which contains a static class called Routes. For example, to navigate to the LOGIN feature, you would use Get.to(Routes.LOGIN).

Overall, this structure promotes modularity and separation of concerns by organizing features into their own modules. The bindings handle dependency injection, the controllers manage the logic, and the views handle the UI rendering. The routing system allows for easy navigation between features using predefined route paths.

Getx Architecture Structure

lib/
├── app/
│ ├── base/
│ │ └── lifecycle_manager.dart
│ ├── config/
│ │ ├── env.dart
│ │ └── env.g.dart
│ ├── core/
│ │ ├── assets/
│ │ │ └── assets.gen.dart
│ │ ├── constants/
│ │ │ └── env_key_constant.dart
│ │ ├── exceptions/
│ │ │ └── exceptions.dart
│ │ ├── extensions/
│ │ │ ├── dartz_x.dart
│ │ │ └── string_x.dart
│ │ ├── failures/
│ │ │ └── failures.dart
│ │ └── network/
│ │ ├── api_endpoint.dart
│ │ └── api_provider.dart
│ ├── modules/
│ │ ├── about/
│ │ │ ├── bindings/
│ │ │ │ └── about_binding.dart
│ │ │ ├── controllers/
│ │ │ │ └── about_controller.dart
│ │ │ └── views/
│ │ │ └── about_view.dart
│ │ ├── create_document
│ │ ├── ...
│ │ ├── splash
│ │ └── verify_token
│ ├── routes/
│ │ ├── app_pages.dart
│ │ └── app_routes.dart
│ ├── shared/
│ │ ├── widgets/
│ │ │ └── statistic_card.dart
│ │ └── models
│ └── utils/
│ └── date_formatter_util.dart
├── generated/
│ └── locales.g.dart
├── app.dart
└── main.dart

Pros:

  1. Modularity: The structure promotes a modular approach, allowing you to organize features into separate modules. This enhances code organization and makes it easier to maintain and scale the application.
  2. Dependency Injection: By utilizing the bindings folder, the structure encourages the use of dependency injection. This improves the testability, flexibility, and maintainability of your application by decoupling dependencies and promoting loose coupling.
  3. Simplified State Management: GetX provides a simple and reactive state management solution. By extending GetxController or GetXController, you can easily manage the state and reactively update the UI components within the feature.
  4. Routing System: GetX offers a built-in routing system that simplifies navigation between features. Defining route paths in app_pages.dart and using Get.to with the predefined route paths in app_routes.dart allows for seamless navigation between features.

Cons:

  1. Lack of Clear Separation of Concerns: While the GetX architecture pattern promotes modularity, it may not enforce a strict separation of concerns between the different layers of the application. As a result, it can be challenging to ensure a clear separation between business logic, UI components, and data handling.
  2. Potential for Bloated Controllers: Without careful management, controllers in the GetX architecture pattern can become bloated with excessive responsibilities, leading to decreased maintainability and readability.
  3. Limited Testability: The reliance on the GetX framework for state management and navigation can make it challenging to write isolated unit tests for controllers and other components. This can hinder the overall testability of the application.

In conclusion, the folder structure based on the GetX architecture pattern offers several advantages, including modularity, dependency injection, simplified state management, and a routing system. These benefits contribute to better code organization and maintainability, allowing for efficient development and scalability.

However, it’s important to be aware of some potential drawbacks. One of them is the lack of clear separation of concerns, which may lead to bloated controllers and reduced code readability. Additionally, the structure might pose challenges in terms of testability, making it harder to isolate and test specific components.

Clean Architecture Structure

lib/
├── base/
│ └── lifecycle_manager.dart
├── config/
│ ├── env.dart
│ └── env.g.dart
├── core/
│ ├── assets/
│ │ └── assets.gen.dart
│ ├── constants/
│ │ └── env_key_constant.dart
│ ├── di/
│ │ ├── service_locator.dart
│ │ └── register_module.dart
│ ├── exceptions/
│ │ └── exceptions.dart
│ ├── extensions/
│ │ ├── dartz_x.dart
│ │ └── string_x.dart
│ ├── failures/
│ │ └── failures.dart
│ ├── i18n/
│ │ ├── strings_id-ID.i18n.json
│ │ ├── strings.i18n.json
│ │ └── translations.g.dart
│ ├── log
│ ├── media_store
│ ├── messengers
│ ├── mixins
│ ├── network/
│ │ ├── http/
│ │ │ ├── interceptors
│ │ │ ├── modules
│ │ │ ├── http_client.dart
│ │ │ └── http_module.dart
│ │ ├── api_endpoint.dart
│ │ ├── api_error_type.dart
│ │ └── api_exceptions.dart
│ └── use_cases
├── features/
│ ├── auth/
│ │ ├── data/
│ │ │ ├── data_sources
│ │ │ ├── models
│ │ │ └── repositories
│ │ ├── domain/
│ │ │ ├── entities
│ │ │ ├── repositories
│ │ │ └── use_cases
│ │ └── presentation/
│ │ ├── bloc
│ │ ├── pages
│ │ └── widgets
│ ├── create_document
│ ├── mail
│ ├── notification
│ ├── profile
│ └── recovery
├── router/
│ ├── guards/
│ │ ├── auth_guard.dart
│ │ └── guest_guard.dart
│ ├── observers
│ ├── results
│ ├── bmpr_deeplink.dart
│ └── bmpr_router.dart
├── shared/
│ ├── widgets/
│ │ └── statistic_card.dart
│ └── models
├── utils/
│ └── date_formatter_util.dart
├── app.dart
├── injection.dart
└── main.dart

The new folder structure follows the clean architecture pattern, which emphasizes separation of concerns and maintainability. Let’s take a closer look at each layer:

Features Folder:

  • The features folder serves as the main container for all the features in your application.
  • Each feature has its own subfolder, which consists of the data layer, domain layer, and presentation layer.
  • The data layer handles data-related operations, such as data sources, repositories, and data models.
  • The domain layer contains the business logic and domain-specific entities, use cases, and interfaces.
  • The presentation layer focuses on the UI components, such as blocs, pages, and widgets, responsible for rendering the feature's views.

Router Folder:

  • The router folder contains the routing configuration for your application.
  • The bmpr_router.dart file, extending auto_route, defines the routes and their corresponding handlers.
  • This router class is registered into GetIt, a dependency injection container, to allow easy access to the router throughout the application.
  • Usage of the router involves calling methods such as push or pushNamed to navigate to specific routes. For example: getIt<BMPRRouter>().push(LoginRoute()).

Other Folders:

  • The base folder can be used for base classes or utilities that are shared across different parts of the application.
  • The config folder contains configuration-related files, such as environment variables or settings.
  • The core folder encompasses core functionalities, such as assets, constants, exceptions, and network-related components.
  • The shared folder holds shared components or widgets that can be reused across different features.
  • The utils folder is reserved for utility classes or helper functions that provide common functionality.

By adopting the Clean Architecture pattern, your folder structure emphasizes the separation of concerns, maintainability, and testability of your codebase. Each feature is organized into distinct layers, enabling clear boundaries between data, domain, and presentation. The router plays a crucial role in handling navigation within the application.

This structure, inspired by Reso Coder’s Clean Architecture, promotes code scalability, ease of maintenance, and a clear separation of responsibilities. It allows for better code organization and enables developers to focus on writing clean and modular code.

Pros:

  1. Modularity and Separation of Concerns: The folder structure follows the principles of Clean Architecture, enabling clear separation between layers and promoting modularity. Each feature has its own designated folders, making it easier to understand and maintain code related to that feature.
  2. Scalability and Maintainability: With a clear separation of concerns, it becomes easier to scale the application and add new features. Changes in one layer won’t affect other layers, allowing for easier maintenance and updates.
  3. Testability: The Clean Architecture promotes testability by separating business logic from external dependencies. Each layer can be tested independently, making it easier to write unit tests and ensure the correctness of the application.
  4. Code Reusability: The modular structure encourages code reusability. Components developed for one feature can be easily reused in other features or projects, reducing development time and effort.
  5. Simpler Navigation: The use of the bmpr_router.dart file, integrated with auto_route and registered with GetIt, simplifies the navigation process. It provides a consistent and centralized way to handle routing and navigation within the application.

Cons:

  1. Initial Learning Curve: Adopting the Clean Architecture approach and the new folder structure may require developers to familiarize themselves with the principles and conventions. This initial learning curve might be a challenge for those who are new to the pattern.
  2. Increased Complexity for Smaller Projects: The Clean Architecture approach and the associated folder structure might introduce additional complexity, which may not be necessary for smaller projects or applications with simpler requirements.
  3. Potential Over-Engineering: It’s important to strike a balance between code organization and the complexity introduced by adhering strictly to the Clean Architecture principles. Over-engineering can lead to unnecessary complexity and hinder productivity.
  4. Dependency Injection Setup: While the use of GetIt for dependency injection provides flexibility and modularity, setting it up and managing dependencies can be challenging, especially for larger projects with many dependencies.
  5. Potential Overhead: The layered structure of Clean Architecture can introduce some additional overhead in terms of file organization and communication between layers. It’s essential to strike a balance between maintaining a clean architecture and avoiding excessive overhead.

Overall, adopting the Clean Architecture approach and the new folder structure brings several benefits, such as modularity, maintainability, and testability. However, it’s crucial to consider the size and complexity of your project and make sure the chosen approach aligns with your specific needs and goals.

Remember that these pros and cons may vary depending on the specific requirements and characteristics of your project. It’s essential to evaluate them in the context of your application and team’s capabilities.

Comparison of Getx Architecture Pattern vs Clean Architecture Pattern:

The Clean Architecture pattern, on the other hand, focuses on a more strict separation of concerns and maintaining a clear architecture with distinct layers. Here are some key points to consider:

  • Separation of Concerns: Clean Architecture emphasizes a clear separation between business logic, UI, and data layers. It provides a well-defined structure with entities, use cases, and repositories, allowing for better maintainability and testability.
  • Testability: The Clean Architecture pattern promotes testability by allowing each layer to be tested independently with proper abstractions and interfaces. This enables developers to write unit tests for business rules and data operations more effectively.
  • Flexibility and Scalability: Clean Architecture allows for easier modification and evolution of individual layers without impacting other parts of the system. It provides flexibility and scalability by reducing dependencies and coupling between different layers.
  • Learning Curve: Implementing Clean Architecture requires a deeper understanding of the underlying principles and may have a steeper learning curve compared to the GetX architecture pattern.
  • Code Organization: Clean Architecture offers a more structured and standardized way of organizing code, making it easier for developers to navigate and maintain the codebase in the long run.

In summary, the GetX architecture pattern provides modularity, simplicity, and a built-in routing system for Flutter applications. However, it may lack strict separation of concerns and can lead to bloated controllers. On the other hand, the Clean Architecture pattern emphasizes clear separation of concerns, testability, and code organization, but it requires a deeper understanding and has a more structured approach.

In the next part of this article, we will explore how auto_route, a powerful routing package, can replace the routing functionality provided by GetX in our Flutter project. AutoRoute offers its own set of benefits and features that can enhance our application’s navigation experience. We will dive into the installation process, demonstrate how to define routes, and showcase the advantages of using AutoRoute over GetX for routing purposes. Stay tuned for an exciting exploration of this fantastic package in the upcoming part!

--

--