Stacked State management in Flutter

eloquentcoder

Patrick Obafemi

Posted on June 4, 2022

Stacked State management in Flutter

Few months ago i came across a youtube video by Filled Stacks about a state management system he and his team came up with that uses a MVVM approach to state management and i decided to give it a try. Now i must say that coming from a flutter bloc background i needed something with less boilerplate code and less setup(kudos to the flutter_bloc team as they did an amazing job!). And i must say i was totally impressed! You can take this as a review of the stacked package or a basic intro but either is fine!!

So let's start by creating a new flutter project by running the command

flutter create flutter_stacked
Enter fullscreen mode Exit fullscreen mode

that will provision a new flutter project. Then we will install the stacked package from pub.dev. As of the time of writing the current version is 2.3.9 but you can see the latest version here. Personally i like installing packages using the command below

flutter pub add *package name*
Enter fullscreen mode Exit fullscreen mode

this will install the latest compatible version to your project. When that is done you get the notorious default counter app that comes with every new flutter app. We would do the same counter app but with the stacked package. So let's start by removing default code and creating a stateless CounterApp Widget. We will be left with this

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CounterApp(),
    );
  }
}

class CounterApp extends StatelessWidget {
  const CounterApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold();
  }
}
Enter fullscreen mode Exit fullscreen mode

this will give us a white screen. Great! So the first thing we will do is create a viewmodel. The viewmodel is a class where all the state and state modification is stored. So instead of using up RAM and using stateful widget you just create a viewmodel that corresponds to your widget. In a production app you can create a folder for your feature and then create a viewmodel folder and a view folder but we will do everything in our main.dart file. So creating a viewmodel will look like this;

import 'package:stacked/stacked.dart';

class CounterViewModel extends BaseViewModel {
    int counter = 0;
}
Enter fullscreen mode Exit fullscreen mode

your viewmodel will extend BaseViewModel(there are other types of viewmodel for other purposes and you can check out the package documentation) and we will initialise a counter property and set it to 0. The next step will be to wire up the viewmodel to the CounterApp widget. There are a couple of ways to do this but my favorite way is this....

class CounterApp extends StatelessWidget {
  const CounterApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<CounterViewModel>.reactive(
      viewModelBuilder: () => CounterViewModel(),
      builder: (context, CounterViewModel viewModel, child) {
        return const Scaffold();
      }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

So the first step is to wrap your scaffold with a viewmodelBuilder.reactive widget constructor that deals with rebuilding your widget on state change. It has a viewModelBuilder property that returns a function returning your viewModel and a builder function that takes the context, an instance of the viemodel and a third child parameter.
The next step is to create a center widget that will have a text widget child that displays the counter variable.

  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<CounterViewModel>.reactive(
        viewModelBuilder: () => CounterViewModel(),
        builder: (context, CounterViewModel viewModel, child) {
          return Scaffold(
            body: Center(
              child: Text(viewModel.counter.toString()),
            ),
          );
        });
  }
Enter fullscreen mode Exit fullscreen mode

the viewmodel instance exposes the counter variable and we display it in the text widget. The next step is to create a floating action button that increases the counter while we click on it.

return Scaffold(
   floatingActionButton: FloatingActionButton(
                onPressed: () => viewModel.incrementCounter(),
                child: const Icon(Icons.add)),
        body: Center(
          child: Text(viewModel.counter.toString()),
   ),
);
Enter fullscreen mode Exit fullscreen mode

The floating action button calls an incrementCounter function we have not created yet on the CounterViewModel. Let's go create that now.

class CounterViewModel extends BaseViewModel {
  int counter = 0;

  void incrementCounter() {
    counter++;
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode

as we can see the incrementCounter function increases the counter variable and calls notifyListeners(). This will seem familiar to devs who use provider package. Clicking this button increases the counter variable and rebuilds it in your widget. The whole code will look like this

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const CounterApp(),
    );
  }
}

class CounterApp extends StatelessWidget {
  const CounterApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<CounterViewModel>.reactive(
        viewModelBuilder: () => CounterViewModel(),
        builder: (context, CounterViewModel viewModel, child) {
          return Scaffold(
            floatingActionButton: FloatingActionButton(
                onPressed: () => viewModel.incrementCounter(),
                child: const Icon(Icons.add)),
            body: Center(
              child: Text(viewModel.counter.toString()),
            ),
          );
        });
  }
}

class CounterViewModel extends BaseViewModel {
  int counter = 0;

  void incrementCounter() {
    counter++;
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode

We have not only reduced our code and removed boilerplate code but we have a very simple and easy to understand state management platform. Stacked package is actively maintained and regularly updated. There are so many functions and classes that would fit your use case so you can check them out here. I am not affliated to them in any way but i would highly recommend them to any flutter dev of any experience level. Cheers and happy coding!

💖 💪 🙅 🚩
eloquentcoder
Patrick Obafemi

Posted on June 4, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related