Introduction to Stacked for Flutter

Engineering

Flutter

State Management

Summary

The Stacked Flutter framework helps developers create scalable, tested, and maintainable applications through MVVM architecture and clean code principles. It provides a CLI, state management, dependency inversion, and integrated tools for UI components, navigation, and testing. Whether working solo or in teams, developers can efficiently build production-ready apps using Stacked's comprehensive features.

Key insights:
  • Scalability: Designed to support large projects, Stacked facilitates seamless feature integration and team collaboration with clear coding rules.

  • Testability: The MVVM architecture enables straightforward unit testing, ensuring reliable updates and robust applications.

  • Maintainability: Clear separation of concerns minimizes tangled code, promoting modular and easily upgradable architecture.

  • CLI Efficiency: The Stacked CLI automates repetitive tasks, expediting the setup of views, services, and navigation.

  • State Management: Built-in tools ensure synchronization between UI and business logic for cleaner and more maintainable applications.

  • Dependency Injection: Supports modular code with service locators, simplifying testing and reducing code coupling.

  • Environment Configurations: Allows tailored dependency setups for development, production, and other environments.

  • Advanced Navigation: Offers tools like RouterService and AutoRoute compatibility for seamless web and mobile navigation.

  • Startup Logic: Simplifies app initialization with dedicated workflows for authentication, configuration, and state setup.

  • ViewModel Options: Provides Reactive, Stream, and Future ViewModels for diverse application requirements and dynamic updates.

Introduction

Developing scalable and stable apps in the rapidly changing field of Flutter development can be challenging, particularly when working with teams or maintaining sizable codebases. By offering a strong framework that encourages clean code architecture and effective development techniques, Stacked for Flutter seeks to make this process easier. Stacked, created by the respected mobile development company FilledStacks, is intended to assist developers in streamlining their processes and is based on years of expertise in creating cross-platform applications. Stacked provides the tools and protocols required to guarantee that your Flutter projects are scalable, tested, and maintained from beginning to end, regardless of the size of your project—from a small prototype to a large-scale production application.

This insight will give a summary of the main characteristics of the Stacked framework, examining its fundamental ideas—such as the Model-View-ViewModel (MVVM) architecture—and how they help create applications of superior quality. We will discuss the framework's emphasis on developer productivity, scalability, testability, and maintainability, as well as its potent Command Line Interface (CLI) for task automation. We will also explore how Stacked makes state management, navigation, and dependency inversion easier, which makes it an essential tool for teams and lone developers alike. You will have a thorough grasp of how Stacked may improve your Flutter development experience and assist you in producing dependable, production-ready apps by the end of this article.

Stacked for Flutter: Introduction and Features Overview

A powerful framework called Stacked was created to make it easier to create Flutter apps of production quality. Stacked, developed by seasoned mobile development firm FilledStacks, combines years of expertise in creating software that is scalable, tested, and maintainable on various platforms, including Native iOS, Android, and Xamarin. By offering organized tools and norms, it seeks to empower both teams and lone developers, enabling effective and accessible high-quality Flutter development.

Fundamentally, Stacked makes use of a highly subjective design based on the Model-View-ViewModel (MVVM) pattern. As applications get more sophisticated, this makes it possible for developers to efficiently divide concerns, guaranteeing cleaner codebases. Stacked gives you all you need for smooth development, testing, and maintenance, regardless of how big or little your project is. Here is a detailed description of the features:

1. Scalability: Enabling Teams to Thrive

Stacked is ideal for teams looking to preserve quality and productivity as projects grow since it was designed with scalability in mind. New innovations are guaranteed to integrate effortlessly without affecting the application's structure because of its well-defined coding rules and architectural guidelines. Stacked eases the onboarding process for new team members and promotes seamless developer cooperation by offering a clear development path.

2. Testability: Building Confidence in Code

A fundamental component of Stacked's design philosophy is unit testing. Teams can create and maintain reliable tests by using the MVVM design, which streamlines the process of testing state management and business logic. By emphasizing testability, developers may confidently release features and upgrades with little risk, increasing the dependability of applications.

3. Maintainability: Sustaining Code Quality Over Time

Stacked's emphasis on the separation of concerns helps to alleviate the daunting task of maintaining a big codebase. A stacked application lowers the possibility of tangled or "spaghetti" code as the project progresses because each component has a clearly defined function. Following its stringent code guidelines guarantees that your program will always be clear, modular, and simple to upgrade.

4. Convenience: Powered by a CLI

Stacked provides a robust Command Line Interface (CLI) that automates boilerplate code production to significantly increase productivity. The CLI speeds up the development process by facilitating the creation of views, services, state management, and navigation. This allows teams to concentrate on designing things that have an impact rather than tedious setup work.

5. Essential Tools: From State Management to Unit Tests

Out of the box, Stacked provides all the foundational tools needed for production-ready apps:

State Management: Offers an organized method for controlling the state of the application, guaranteeing that the business logic and user interface stay in sync and are simple to maintain.

Start-Up Logic: Simplifies app launch workflows by incorporating built-in mechanisms to manage initialization activities, such as loading configurations or authenticating users.

Navigation: Provides a simplified approach to screen transition management, guaranteeing seamless and manageable application navigation flows.

Dialog and BottomSheet UI Builders: These reusable, adaptable builders make it easier to create dialogs and bottom sheets while lowering UI boilerplate and improving uniformity.

Dependency Inversion: Facilitates modular code and easier testing by effectively handling dependencies, which promotes loose coupling between components.

Unit Testing Templates: Offers pre-made templates to make it easier to write unit tests for state and business logic, encouraging dependable and strong application development techniques.

These capabilities enable developers to concentrate on effectively providing user value by streamlining the intricacies of Flutter app development.

As a complete framework designed for developers that appreciate scalability, maintainability, and high-quality code, Stacked stands out. Its organized methodology and extensive tooling make it a game-changer for creating Flutter applications that are ready for production. Stacked lays the groundwork for safely creating dependable and scalable software with features like a dedicated CLI and an emphasis on testing and concern separation.

How Does Stacked Work?

By requiring a clear division between the application's core and user interface (UI), Stacked streamlines state management in Flutter apps. It is simpler to expand the program as needs change because of this separation, which guarantees maintainability, testability, and scalability. Fundamentally, Stacked makes use of the View-ViewModel relationship, in which ViewModels control state and logic while Views handle the user interface. With less boilerplate code, this method guarantees a strong state management procedure. Let us examine how this framework works, demonstrating the procedure with an example counter application.

1. View Creation: Building the UI with StackedView

The first step in creating a stacked application is creating Views, which are the parts in charge of rendering the user interface. Stacked presents StackedView, a class intended to link the user interface to the related ViewModel, as an alternative to the traditional StatelessWidget or StatefulWidget.

Here’s an example of a simple CounterView:

class CounterView extends StackedView<CounterViewModel> {
   @override
   Widget builder(
     BuildContext context,
     CounterViewModel viewModel,
     Widget? child,
   ) {
     return Scaffold(
       body: Center(
         child: Text(
           viewModel.counter.toString(),
           style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
         ),
       ),
       floatingActionButton: FloatingActionButton(
         onPressed: viewModel.incrementCounter,
         child: const Icon(Icons.add),
       ),
     );
   }
    @override
   CounterViewModel viewModelBuilder(BuildContext context) => CounterViewModel();
 }

The key concepts in the view shown above are:

ViewModel Binding: The viewModelBuilder function constructs the ViewModel, and it binds it to the View.

Builder Function: This is where the UI is defined, it leverages the state managed by the ViewModel.

UI Updates: Any state changes in the ViewModel automatically trigger UI rebuilds through the rebuildUi function (more on this in the next part).

This design ensures that UI files remain focused solely on visual elements and user interactions.

2. ViewModel: Managing State with BaseViewModel

The ViewModel in Stacked handles the application’s logic and state. By extending BaseViewModel, it gains access to tools like rebuildUi. It facilitates seamless UI updates.

Here’s the CounterViewModel for the counter app:

class CounterViewModel extends BaseViewModel {
   int _counter = 0;
    int get counter => _counter;
    void incrementCounter() {
     _counter++;
     rebuildUi(); // Triggers a UI rebuild with updated state
   }
 }

The key concepts in the ViewModel shown above include:

Encapsulation of State: Private variables (e.g., _counter) are exposed via getters, ensuring controlled access to state.

State Updates: Functions like incrementCounter modify the state and invoke rebuildUi, which notifies the View to refresh.

Decoupling Logic from UI: All logic resides in the ViewModel. This makes it easy to test and maintain.

3. Workflow: Binding UI to Logic

Stacked’s View-ViewModel interaction follows a clear and intuitive workflow the follows the following steps:

ViewModel Initialization: The viewModelBuilder in the View constructs the associated ViewModel.

State Binding: The View uses the ViewModel's state to build the UI.

User Interaction: User actions (for example button taps), invoke functions in the ViewModel.

State Updates: The ViewModel processes the interaction, updates the state, and calls rebuildUi.

UI Rebuild: The updated ViewModel triggers the builder function, refreshing the UI with the new state.

This workflow ensures a consistent and clean separation of concerns. This makes the application easier to maintain and scale.

4. Navigation

To integrate the counter functionality into the app, the navigationService in startup_viewmodel.dart must be updated:

_navigationService.replaceWithCounterView();

With this modification, the counter interface will be shown when the app is launched, and tapping the FloatingActionButton will increase the counter that is visible on the screen.

The outcome is a succinct and manageable implementation of a simple counter that highlights the strength and ease of use of Stacked's state management.

The foundation of Stacked's architecture is the clear division and natural connection between the state and user interface. Developers may create scalable and testable apps with ease by abstracting logic into ViewModels and concentrating Views just on rendering. The counter application demonstrates the core ideas of Stacked, providing a foundation for handling more intricate state management situations in Flutter applications of production quality.

Stacked Startup Logic: An Organized Approach to Initializing Your App

Before the user may see the core functionality of any application, a few activities need to be completed. These activities, which are frequently referred to as "startup logic," involve conducting configuration checks, initializing services, and verifying user credentials. By offering a StartupView and StartupViewModel—dedicated components where such logic can be implemented—Stacked streamlines this procedure. In order to demonstrate how Stacked's startup logic operates, this section walks through an example that dynamically switches the user between views according to their login status.

1. How Stacked Startup Logic Operates

Stacked’s startup process follows a structured workflow. In our example, we will implement a simple app according to the following logic:

Application Launch: The app launches and displays the StartupView.

Execution of Logic: The runStartupLogic function in StartupViewModel is called to perform preliminary checks.

User Authentication: The application queries the AuthenticationService to determine the user's login status.

Conditional Navigation: If the user is logged in, the app navigates to the HomeView. Otherwise, the app navigates to the LoginView

2. Creating Views for Conditional Navigation

To set up navigation, the app needs two views: HomeView and LoginView. These views are created using the Stacked CLI for consistency and speed.

HomeView:

The HomeView serves as the main screen for logged-in users. Its background is set to purple to differentiate it visually.

class HomeView extends StackedView<HomeViewModel> {
   const HomeView({Key? key}) : super(key: key);
    @override
   Widget builder(
     BuildContext context,
     HomeViewModel viewModel,
     Widget? child,
   ) {
     return Scaffold(
       backgroundColor: Colors.purple,
       body: Container(
         padding: const EdgeInsets.all(25.0),
       ),
     );
   }
    @override
   HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
 }

LoginView:

The LoginView is designed for users who need to log in. Its background is set to red.

class LoginView extends StackedView<LoginViewModel> {
   const LoginView({Key? key}) : super(key: key);
    @override
   Widget builder(
     BuildContext context,
     LoginViewModel viewModel,
     Widget? child,
   ) {
     return Scaffold(
       backgroundColor: Colors.red,
       body: Container(
         padding: const EdgeInsets.all(25.0),
       ),
     );
   }
    @override
   LoginViewModel viewModelBuilder(BuildContext context) => LoginViewModel();
 }

3. Implementing the Authentication Service

The AuthenticationService determines the login status. For simplicity, the initial implementation returns a static value. This can be replaced with a real authentication mechanism. 

class AuthenticationService {
   bool userLoggedIn() {
     return true; // Set to false to test navigation to LoginView
   }
 }

This service is registered via dependency injection to make it accessible throughout the app. To streamline this process, you can use the stacked create service command to generate the service files. This ensures that the service is automatically registered with the locator, simplifying setup and reducing manual configuration.

4. Writing Startup Logic in the ViewModel

The StartupViewModel is the heart of the startup process. It retrieves the AuthenticationService and NavigationService to execute the logic.

class StartupViewModel extends BaseViewModel {
   final _authenticationService = locator<AuthenticationService>();
   final _navigationService = locator<NavigationService>();
    Future runStartupLogic() async {
     if (_authenticationService.userLoggedIn()) {
       _navigationService.replaceWith(Routes.homeView);
     } else {
       _navigationService.replaceWith(Routes.loginView);
     }
   }
 }

In this code the AuthenticationService checks the login status, while the NavigationService handles view transitions. Moreover, depending on the user's login state, the app navigates to HomeView or LoginView.

The startup logic of Stacked provides an organized method for handling pre-launch activities. The framework guarantees maintainability and extensibility by assigning tasks to specialized services and dividing concerns between the StartupView and StartupViewModel. Stacked offers the resources to do the task quickly, whether the objective is user authentication, configuration initialization, or app state preparation. This fundamental knowledge lays the groundwork for more intricate situations that can be constructed using this strong structure.

Advanced Concepts in Stacked Architecture

We will examine more complex Stacked ideas in this section, with an emphasis on ViewModel administration, dependency injection, and service organization. Each of these ideas is essential to creating Flutter apps that are strong, scalable, and maintained.

1. What is a Service Locator?

An application's dependencies can be managed using the Service Locator design pattern. Components communicate with a centralized registry or service locator to dynamically retrieve necessary services or components rather than having them instantiate dependencies directly. By greatly reducing the tight connectivity between components, this pattern increases the system's flexibility and modularity.

The Service Locator facilitates simpler code maintenance by centralizing dependency management. It also makes sure that dependencies may be easily swapped or changed, which is essential for scalability. Components will use the Service Locator to retrieve instances of a service rather than directly referencing its implementation, making it simpler to maintain and adjust these dependencies.

The StackedApp annotation is essential to Stacked because it makes it possible to define dependencies using DependencyRegistration objects. You can effectively arrange and retrieve dependencies within your application by utilizing these registrations. An illustration of this configuration might be:

@StackedApp(
   dependencies: [
     Singleton(classType: MyService),
   ],
 )

These are the benefits of the service locators include:

Centralized Dependency Management: All dependencies are managed from a single location, making it easier to control and update them across the app.

Simplified Testing: Mock injections become much easier, facilitating unit testing by enabling mock services to be injected in place of real ones.

Promotes Reusability and Cleaner Architecture: Dependencies can be reused across different parts of the application, reducing redundancy and promoting better architectural practices.

2. Dependency Types in Stacked

Dependencies in Stacked can be registered in a variety of methods to regulate how they behave. Depending on how you wish to handle the lifecycle of your dependencies, each sort of registration has a distinct function:

Singleton: Singleton: This guarantees that during the course of an application's lifecycle, only one instance of a class is produced and used. When the instance is first fetched, it is initialized and kept for all subsequent calls. It has the following parameters:

The pattern accepts several key parameters for configuration. The classType parameter defines which concrete class implementation should be used. For flexibility in abstraction, you can specify an asType parameter that maps to an abstract interface. When you need more sophisticated initialization logic, you can provide a custom callback through the resolveUsing parameter.

LazySingleton: A Singleton variant in which an instance is only created upon initial use. This aids in memory conservation, particularly for services that might not be required right away.

InitializableSingleton: You can specify initialization logic that is carried out when the instance is first created with the InitializableSingleton type. For services that need setup or asynchronous operations, such as database startup or API configuration, this is especially helpful.

Factory: A fresh instance is made each time a service request is made. For stateless services, this guarantees that no shared state exists across various application components.

FactoryWithParam: This parameterized factory has the advantage of dynamically supplying parameters at runtime and generates a new instance each time it is invoked.

3. Environments in Stacked

One useful feature that Stacked provides is the ability to make dependencies environment-specific. This implies that depending on the context your application is running in—development, production, or custom environments—you can build up distinct sets of dependencies. This makes it possible to have more precise control over the setup of the app at various phases of development.

By using the setupLocator function, you can pass in the desired environment and configure dependencies accordingly. For instance:

@StackedApp(
   dependencies: [
     Singleton(classType: NavigationService, environments: {Environment.dev}),
   ],
 )

Because of this flexibility, you may use customized configurations for production or mock services for testing without having to manually modify the code for every environment. Maintaining a clear division between the various developmental stages is another benefit.

4. What is a ViewModel?

In the Stacked framework, a ViewModel serves as a bridge connecting your application's business logic and user interface (view). In addition to managing user interactions, it is in charge of storing and maintaining the application's state. To maintain a clean architecture, the ViewModel makes sure that the display logic is kept apart from the essential business logic.

BaseViewModel, a base class offered by Stacked, has fundamental functions including error handling and busy state management. This guarantees that the state management and user interface interactions are managed effectively while letting you concentrate on the core of your application. These are the properties of BaseViewModel:

Busy State Handling: By alerting users while a task is ongoing and giving them feedback during lengthy procedures, the runBusyFuture method assists in managing loaded states.

Error Handling: It ensures that any problems are handled and shown correctly in the user interface by offering a means of identifying faults and linking them to certain tasks.

Here’s an example of how you might use a ViewModel:

class MyViewModel extends BaseViewModel {
   Future fetchData() async {
     await runBusyFuture(myApiService.getData());
   }
 }

 

In addition, there are the following special types of ViewModels:

ReactiveViewModel: This kind of ViewModel is perfect for applications that need real-time updates since it automatically responds to modifications in the services it listens to.

StreamViewModel: This ViewModel, which is intended to handle real-time data, modifies the user interface (UI) in response to data changes over time by subscribing to streams.

FutureViewModel: Ideal for jobs that only need to retrieve data once, like calling APIs. The task must be finished before the state is updated, according to this kind of ViewModel.

5. What is a Service?

ViewModels no longer have to handle complex functionality and operations directly because a service in the Stacked framework encapsulates them. ViewModels can concentrate solely on state management and user interface interaction by assigning business logic to services, which improves codebase cleanliness and maintainability.

For instance, a particular kind of service called a facade service serves as an abstraction layer to make interacting with external dependencies, such as databases or HTTP clients, easier. By enabling the usage of mock services, it makes the code easier to test and guarantees that ViewModels are maintained clean and free from direct interactions with lower-level processes. Key benefits of the Facade service are Separation of Concerns (ViewModels focus on state management while the service handles the business logic), Testability and Reusability (common logic is abstracted into services, reducing redundancy across ViewModels).

Here’s an example of how a service is used in Stacked:

class ApiService {
   Future<List<Artist>> getArtists() async {
     // HTTP call and response parsing
   }
 }
  class HomeViewModel extends BaseViewModel {
   final ApiService _apiService = locator<ApiService>();
    Future fetchArtists() async {
     var artists = await _apiService.getArtists();
   }
 }

Your architecture will stay modular if you assign complicated activities to services, which will make it simpler to scale, test, and manage your application.

6. Routing Setup in Stacked

A major change in Stacked's navigation strategy is represented by the introduction of the RouterService, especially in light of Flutter's growing web capabilities. Stacked did not enable web development when it was first created for app-only use, but when Flutter Web emerged as a promising framework for creating applications, it became evident that better web navigation support was required. Similar to the NavigationService, the RouterService is designed to manage web-specific navigation scenarios, especially URL-based navigation. To completely incorporate it into the Stacked architecture, one more setup step is needed than for the NavigationService.

The first step is to swap out the NavigationService in the app.dart file for the RouterService in order to set it up for web navigation. In the dependencies section, the RouterService must be registered and the routes must be defined. To enable the use of the navigator2 option for the Stacked router generator and guarantee compatibility with the v2 Flutter navigator, a build.yaml file needs to be produced in the project root. If the project is created using the CLI's —t web option, this configuration is automatically included. An illustration of how this setup could appear is shown below:

@StackedApp(
   routes: [ ... ],
   dependencies: [
   LazySingleton(classType: RouterService),
   LazySingleton(classType: DialogService),
   LazySingleton(classType: BottomSheetService),
   ]
)
class App {}

This configuration preserves compatibility with mobile platforms while allowing the use of URL-style navigation, which is more appropriate for web applications. The Stacked team intends to eventually replace the NavigationService across all platforms with the RouterService.

However, at Walturn, we advise utilizing AutoRoute instead of RouterService because we have experienced problems with it in several situations. AutoRoute is a better option for projects needing significant online navigation capabilities since it offers more dependable support for sophisticated navigation configurations and robust functionality.

Conclusion

To sum up, Stacked offers an organized framework that improves the Flutter application development process. Because of its emphasis on scalability, testability, and maintainability, developers can create high-caliber, production-ready apps with confidence. Writing clean, testable code is made easier for developers by Stacked's adoption of the MVVM architecture, which keeps business logic and user interface issues apart. The development workflow is streamlined by the addition of a robust CLI and necessary tools like state management and navigation, which greatly reduce repeated chores and increase productivity.

Stacked provides a solid way for teams and individual developers to create Flutter apps that are effective, scalable, and maintainable. It is the best option for anyone looking to create software that can expand to meet their demands because of its modular design, integrated testing capabilities, and explicit rules. Stacked continues to be a useful tool for developers who want to create dependable apps with little hassle as Flutter development advances, providing a strong basis for both small projects and large-scale enterprise solutions.

References

Overview | The Production Flutter Framework (2023) Filledstacks.com. Available at: https://stacked.filledstacks.com/docs/getting-started/overview.

stacked | Flutter Package (no date) Dart packages. Available at: https://pub.dev/packages/stacked.

Other Insights

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Got an app?

We build and deliver stunning mobile products that scale

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?

© Walturn LLC • All Rights Reserved 2024

Our mission is to harness the power of technology to make this world a better place. We provide thoughtful software solutions and consultancy that enhance growth and productivity.

The Jacx Office: 16-120

2807 Jackson Ave

Queens NY 11101, United States

Book an onsite meeting or request a services?