A beginner’s guide to architecting a Flutter app Suragch Follow Jan 1 · 7 min read

How to use the “stacked” package by FilledStacks

If you are reading this article again for a quick refresher, scroll down to the Summary section at the bottom.

Flutter gives you a lot of freedom to do whatever you want. Sometimes that freedom can be a drawback, though, when you’re starting out. That’s certainly true for state management and app architecture.

I’m a fan of FilledStacks (Dane Mackier) and Reso Coder (Matt Rešetár) for their work in educating the Flutter community about app architecture. FilledStacks especially has designed a model of structuring apps which is accessible to beginners. The tutorials tend to be on the long side, though, and the architecture has also evolved over time. That makes it somewhat difficult for a newcomer to understand what is happening.

My goal in this article is to give you a high level understanding of how to architect you app and then some very practical guidance for doing that using the stacked package by FilledStacks.

Architecture overview

FilledStacks proposes using an MVVM style architecture. The View is usually a widget layout for one screen of your app. It doesn’t contain any logic or state, though. That is contained in the View Model, which doesn’t know any specific details about the View. Most apps need to store and access data and that is handled by Services, which are just Dart classes that abstract away the details so that the View Models don’t need to worry about how it’s done.

You create a new View Model for every screen on your app. The Services are accessible globally, though, so they stay the same. A multi-screen app would be structured something like this:

Practically speaking, the files might be organized something like this:

You are free to use a different organization, though. FilledStacks likes to put the view models in the core folder (at least in the original tutorial). I think I prefer to keep them close to the views that use them, but I usually change my mind for every new app I make. Read this article for more thoughts on this. You can change later when the need arises. All it takes is moving the files and changing the import paths.

Example

I’ll take you through an easy example now to implement the above architecture. I’ll show you how to create a view and it’s associated view model.

I encourage you to actually work through the example yourself rather than just reading it. This will help you to get a better grasp of the concept.

Getting started

Start a new project. Call it whatever you want. You’ll get the default counter app, which we’ll modify to start using the MVVM style architecture with the Provider Architecture package.

In pubspec.yaml, add the stacked package:

dependencies:

stacked: ^1.6.0

Note: You don’t actually have to use the Stacked package to implement this architectural pattern. The advantages, though, are that it removes some boilerplate code and also hides away some of the complexities of the Provider package, which it uses internally. If you would like to get some background of how all this works under the hood, read the original FilledStacks Provider architecture tutorial and also my previous article Making sense of all those Flutter Providers.

For this example I’m not going to create the full folder structure that I showed above, but you can if you want to.

View Model

In the lib/ folder, create a new file called counter_viewmodel.dart. Then paste in the following code:

Note that the class extends ChangeNotifier , which gives you the notifyListeners() method. The view will be a listener, so this method is what the Stacked package will use to rebuild the UI whenever there are changes. ChangeNotifier is part of Flutter foundation , so we don’t actually have any dependencies on the stacked or provider packages here in the view model.

View

Create a new file in the lib/ folder called counter_screen.dart. This is your UI widget layout for the counter screen.

Paste in the following code:

At the top of the build() method you can see ViewModelBuilder . This is what provides the CounterViewModel to the widget tree. Because the state is in the view model, there is no need to use a StatefulWidget . Thus you can see that CounterScreen is a StatelessWidget . It gets the counter value from the view model. When the button is pressed this will call increment() on the view model, which in turn will also trigger a rebuild with the new counter value.

We still have to clean up main.dart. Replace it with the following code:

If you run the app now it should behave exactly like the default counter app.

Pushing the button increments the counter as expected

Benefits of the Provider Architecture package

So far you could have done all that with the plain old Provider package using a ChangeNotifierProvider and a Consumer . A common need, though, is to fetch some initial data from the network or a database. Your view model can expose a method to do these initialization tasks.

Replace counter_viewmodel.dart with the following code:

You can see that now there is a method to fetch some initial data from a web API service. I’m not going to talk about making a service here, so I just included some fake code at the bottom. You can read more about creating an actual service in this article:

The magic of the Stacked package is that ViewModelBuilder has an onModelReady callback that allows you to run some initialization code when the view model is ready.

In counter_screen.dart, add the following argument to ViewModelBuilder :

onModelReady: (model) => model.loadData(),

Now when you run the app it will update automatically after the two second Future completes.

Summary

Here is a cheat sheet for when you just need a simple reminder for how to get set up in a new project.

Dependency

Add the stacked package dependency to pubspect.yaml.

dependencies:

stacked: ^1.6.0

View Model

Create a new file called my_screen_viewmodel.dart and add a Dart class that extends ChangeNotifier :

import 'package:flutter/foundation.dart'; class MyScreenViewModel extends ChangeNotifier {

int _someValue = 0;

int get someValue => _someValue; Future loadData() async {

// do initialization...

notifyListeners();

} void doSomething() {

// do something...

notifyListeners();

}

}

View

Create a new file called my_screen.dart and make your normal widget layout for that screen.

Add a ViewModelBuilder to the top of your MyScreen widget tree. The easiest way to do this is to use a shortcut to wrap the top widget with a new widget. Instead of having a child , though, use => to return the top widget from the builder parameter.

import 'package:stacked/stacked.dart';

@override

Widget build(BuildContext context) {

return ViewModelBuilder<MyScreenViewModel>.reactive(

viewModelBuilder: () => MyScreenViewModel(),

onModelReady: (model) => model.loadData(),

builder: (context, model, child) => MyTopWidget(



// your widget tree class MyScreen extends StatelessWidget {Widget build(BuildContext context) {return ViewModelBuilder .reactive(viewModelBuilder: () => MyScreenViewModel(),onModelReady: (model) => model.loadData(),builder: (context, model, child) => MyTopWidget(// your widget tree ),

);

}

}

Then within your widget tree you can access the view model like this:

model.someValue

model.doSomething()

Going on

There are other needs that you will run into as you continue to use the Stacked package. You can read about the other options in the documentation. My explanation above should be enough to get you started, though.

Here are a couple more options that you may want to check out. I’ve found them to be more complex than the Stacked architecture, but they still have good things to add.

FilledStacks claims that the Provider Architecture can work for apps of any size. Though I haven’t used it in a large app myself yet, I’m confident that it will give beginning developers a strong foundation for building apps and will carry you well into the intermediate and even advanced levels.

If you want to keep looking for another state management option, check out the following video by ResoCoder for a great overview of different state management options:

Because of ResoCoder’s influence I’m think about switching to the Bloc pattern, but so far I’ve used FilledStacks’ pattern successfully in a few different apps. I like it because I can wrap my head around it.