DEV Community

Blazebrain
Blazebrain

Posted on • Updated on

Using Stacked Architecture in Flutter App

The concept of Architecture is one of the most diverse topics in the world of programming today. A lot of solutions have been developed, all aimed at fixing one flaw or the other. App Architecture is one of the topics where personal choice has been advised to come in. If one meets your needs efficiently and enables you to release good, quality code, then go for it.

This article aims to introduce you to Stacked Architecture, an architecture that offers clean, efficient solutions to architecture your next Flutter app. From dependency Injection to the right out of the box services to layered structures that follow the principles of Clean Architecture and a whole lot. To get started on development with Flutter, check out this article from the Flutter team.

Stacked Architecture, developed by Dane Mackier from FilledStacks, is an MVVM Architecture solution that offers a lot of components for building a highly scalable, testable, maintainable, and usable Flutter application. From

  • State Management,
  • Dependency Injection (Dependency Inversion),
  • Navigation Abstraction,
  • Services (Out-of-the-box),

And a whole lot more. All of these are offered by the Stacked Architecture.

State Management

Handling State in Flutter application using Stacked couldn’t be much better. It provides widgets and constructors which control how the state is being passed and managed in the application. Let’s dive into it using a sample app.

The first thing is to create a new Flutter project,

     flutter create intro_to_stacked
Enter fullscreen mode Exit fullscreen mode

This command generates all the files and folders, which lays the foundation of the flutter app.
Next is to add stacked as a dependency in the pubspec.yaml file

    stacked: ^2.2.7
Enter fullscreen mode Exit fullscreen mode

Next is to set up the basic folder structure for the sample app. Go to the lib folder and create a new folder titled home. Inside this, create two files named home_view.dart and home_viewmodel.dart. The homeViewModel file would be responsible for managing the state of the homeView, which is shown to the user. Clean Architecture, which says the view should contain zero logic, is strictly adhered to; the logic resides in the ViewModel.

The view is bound to the ViewModel using the ViewModelBuilder widget, which Stacked offers. This widget takes in 2 parameters which are

  • builder, which builds the UI that has its state in the ViewModel
  • viewModelBuilder, which is the function that would return the ViewModel for this widget.
    import 'package:flutter/material.dart';
    import 'package:stacked/stacked.dart';
    import 'package:intro_to_stacked/ui/views/home/home_viewmodel.dart';

    class HomeView extends StatelessWidget {
     const HomeView({Key? key}) : super(key: key);
     @override
     Widget build(BuildContext context) {
      return ViewModelBuilder<HomeViewModel>.reactive(
       viewModelBuilder:()=> HomeViewModel(),
       builder: (context, model, child) {
        return Scaffold();
       },
      );
     }
    }
Enter fullscreen mode Exit fullscreen mode

The ViewModelBuilder offers two constructors that deal with how the state is managed and subsequently update the UI for the application. They are the .reactive() and the .nonReactive() constructors. The .reactive() rebuilds the UI every time notifyListeners is called in the ViewModel. However, the .nonReactive() constructor rebuilds the UI once, after which it doesn’t rebuild on subsequent calls in the ViewModel.

With that, we’ve set up the view file, moving to the ViewModel, we create a class that extends the BaseViewModel class that stacked provides. The BaseViewModel class provides functionalities used for maintaining the state in the application. In the ViewModel, we have a String that says Stacked is cool (definitely true). This String is the state currently offered by the ViewModel and which would need to be displayed on the screen.

    import 'package:stacked/stacked.dart';

    class HomeViewModel extends BaseViewModel {
     final String _declaration = 'Stacked is soo cool';
     String get myDeclaration => _declaration;

    }
Enter fullscreen mode Exit fullscreen mode

Back to the view, to use this state in our builder, we use the model offered by the builder connecting the state in the ViewModel to the UI screen which uses it.

    Scaffold(
         body: Center(
          child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
           children: [
            const Text('Stacked Introduction'),
            Text(model.myDeclaration),
           ],
          ),
         ),
        );
Enter fullscreen mode Exit fullscreen mode

On updating the state in the application, let’s have a function that updates the text and calls notifyListeners after. We would link this function to the onPressed of a TextButton.

    //In View(home_view.dart)
     TextButton(
      onPressed: () => model.updateDeclaration(),
      child: const Text('Update Text'),
     )

    //In ViewModel (home_viewmodel.dart)
     void updateDeclaration() {
      _declaration = 'I say Stacked is sooo cool';
      notifyListeners();
     }
Enter fullscreen mode Exit fullscreen mode

The notifyListeners call informs the view to rebuild as there has been a state change in the ViewModel.

For a deep dive into the state management solution offered, check out this article provided by the FilledStacks team.

How about Navigation and Dependency Injection?

Stacked provides a direct out the box to navigation without context. Using the NavigationService it gives, we can smoothly declare our navigation in our ViewModel and use them in the view. This NavigationService allows for a clean UI without the interference of logic or complex routing codes. With the aid of the build runner and stacked generator, we can auto-generate routes and smoothly perform navigation.

Let’s get to it.

The first is to add build_runner and stacked_generator to the dev_dependencies section in your pubspec.yaml file. Also, add the stacked_services to the dependency section.

    dependencies:
     stacked: ^2.2.7
     stacked_services: ^0.8.15

    dev_dependencies:
     build_runner: ^2.1.4
     stacked_generator: ^0.5.5
Enter fullscreen mode Exit fullscreen mode

Run the flutter pub get command to get the files locally for use.

Next is to create the new view that we would be routing to. Create a folder inside views and name it profile. Inside this folder, create two files, the profile_view.dart and profile_viewmodel.dart.

In profile_viewmodel.dart, create a class named ProfileViewModel which extends BaseViewModel. In the ViewModel, we declare a String that the app would display on the view.

    import 'package:stacked/stacked.dart';

    class ProfileViewModel extends BaseViewModel {
     String _pageName = 'This is the Profile Page';
     String get pageName => _pageName;
    }
Enter fullscreen mode Exit fullscreen mode

In profile_view.dart, create the stateless widget which returns the ViewModelBuilder and bind it to the ProfileViewModel. This view would contain a similar setup to the HomeView; it would display a Profile View text.

    import 'package:flutter/material.dart';
    import 'package:intro_to_stacked/ui/views/profile/profile_viewmodel.dart';
    import 'package:stacked/stacked.dart';
    class ProfileView extends StatelessWidget {
     const ProfileView({Key? key}) : super(key: key);
     @override
     Widget build(BuildContext context) {
      return ViewModelBuilder<ProfileViewModel>.reactive(
       viewModelBuilder: () => ProfileViewModel(),
       builder: (context, viewModel, child) {
        return Scaffold(
         body: Center(
          child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
            Text(viewModel.pageName),
           ],
          ),
         ),
        );
       },
      );
     }
    }
Enter fullscreen mode Exit fullscreen mode

The next thing is to create a folder in lib named app. Inside this folder, create a new file titled app.dart. In this file would all the code needed for routing along with the dependencies.

Create a class named AppSetup and annotate it with the StackedApp; this class houses the annotation; it does nothing else asides that. The main focus is the annotation as it takes in two parameters, routes, and dependencies.

    @StackedApp()
    class AppSetup {
     /** This class has no puporse besides housing the annotation that generates the required functionality **/
    }
Enter fullscreen mode Exit fullscreen mode

Stacked offers three different types of routes which determines the mode of transition

  • MaterialRoute
  • CupertinoRoute
  • CustomRoute

Using the MaterialRoute, we register the Views present in the app that would use the NavigationService.

In addition to the routes, we would declare the dependencies we would be using in the app, enabling Stacked to create the locator file that would handle the injection of the declared types. We would register the NavigationService as a LazySingleton, which means it won’t be initialized until used for the first time in the app.

    import '../ui/views/home/home_view.dart';
    import '../ui/views/profile/profile_view.dart';
    import 'package:stacked/stacked_annotations.dart';
    @StackedApp(
     routes: [
      MaterialRoute(page: HomeView, initial: true),
      MaterialRoute(page: ProfileView)
     ],
     dependencies: [
      LazySingleton(classType: NavigationService),
     ],
    )
    class AppSetUp {}
Enter fullscreen mode Exit fullscreen mode

As the HomeView is the first page we contact, we set the initial parameter to true.

After setting this up, run the flutter command to auto-generate the needed files for routing.

    flutter pub run build_runner build --delete-conflicting-outputs
Enter fullscreen mode Exit fullscreen mode

This command would generate the files needed for navigation and dependency injection setup.

The last step in this process is to convert the main top-level function of the app into a Future and await the setupLocator function in the app.locator.dart file generated by the command used earlier.

    Future main() async {
     WidgetsFlutterBinding.ensureInitialized();
     await setupLocator();
     runApp(MyApp());
    }
Enter fullscreen mode Exit fullscreen mode

How do we use it?

To make use of the navigation in the HomeView, we create a function in the HomeViewModel that would perform the navigation. First, we declare the locator which would give us access to the Navigation Service we registered earlier.

    import 'package:intro_to_stacked/app/app.router.dart';
    import '../../../app/app.locator.dart';
    import 'package:stacked/stacked.dart';
    import 'package:stacked_services/stacked_services.dart';

    class HomeViewModel extends BaseViewModel {
     String _declaration = 'Stacked is soo cool';
     String get myDeclaration => _declaration;
     final _navigationService = locator<NavigationService>();

     void navigateToProfileView() {
      _navigationService.navigateTo(Routes.profileView);
     }
    }
Enter fullscreen mode Exit fullscreen mode

The next thing is to create a button in the view and pass the function to its onPressed parameter.

     TextButton(
       onPressed: () => model.navigateToProfileView(),
       child: const Text('Go To Profile View'),
     )
Enter fullscreen mode Exit fullscreen mode

Save the files and run the app; you would see the button there, click the button and see the seamless transition that we achieve using the NavigationService Stacked provides.

Services

Stacked offers several services which can be set up and used within the app. These services include

  • NavigationService
  • BottomSheetService
  • DialogService
  • SnackbarService

These services can be set up and customized to fit various use cases, and similar to the NavigationService, they can be registered and used within the app on the fly. For further information, check out the package on pub.dev and also the articles on FilledStacks.

Conclusion

Stacked Architecture is a super pack ready to be discharged and used to build clean, scalable applications on the fly. This article has explained some of the concepts and components of Stacked Architecture, and with it, we have created a sample app that uses these components. Check out FilledStack for further information on the various features that make up the Stacked Architecture.

You can find the code for the sample app here. If you have any questions, don't hesitate to reach out to me on Twitter: @Blazebrain or LinkedIn: @Blazebrain.

Cheers!

Top comments (5)

Collapse
 
blazebrain profile image
Blazebrain

If you have any questions or enquiries, you can drop them here.

Collapse
 
toogood208_14 profile image
TG

this is a nice article, how do i manage app state in navigation, for example, i want to show the the onboarding page for first time user, but after that navigate to sign up
and if the user have already signed up, navigate to login and if the user have logged in once, navigate to home screen

Collapse
 
blazebrain profile image
Blazebrain

Hi TG,

You can perform the check in the startup_viewmodel file and then route the user to the corresponding screen based on the result. If you save your tokens to local storage after successful login, you can check if the token exist in the startup_viewmodel file and route your user based on this. Another option is to set a boolean to true once logged in and false when logged out, then you route your user based on the value present in this boolean at the start of the application.

Thread Thread
 
hombregeek profile image
hombregeek • Edited

Thanks for this post, very interesting, I've got several ideas from it.

The solution when you tap an option to logout, returns to login page; we can use a flag to check is the token activated to allow this; but what about if the user tap on phone back button, it shows the early page. How can we avoid this behavior?

Thanks in advanced.

Update: I thought I found the solution replace _navigationService.navigateTo(Routes.homeView) for _navigationService.clearStackAndShowRoutes.homeView), I'm making test with this.

Collapse
 
nakubuo profile image
Nathaniel Akubuo

Nice one Blaze