What scoped_model
does is it makes working with state in your app much easier.
Note: you can check out the video for this post here and the longer, pro video here.
While it's all well and good to exclusively use StatefulWidget
, most of the time your app is going to have multiple widgets that need to use the same shared state. The issue with this is that when those widgets exist in various places in our app, passing around state becomes pretty cumbersome. In other words, scoped_model
makes it easy for various widgets to access the same shared state. There are also some nice additional benefits you get as well: _ It allows us to conveniently consolidate app-wide state variables and business logic. _ We can avoid reading up on overly complex architecture patterns. _ Very minimal boilerplate code is required, compared to similar libraries. ## How Does it Work, Exactly? scoped_model
consists of three concepts. First, it offers a Model
class. We add whatever state variables we want as well as business logic as well. Next, for each screen that needs to access this state, we wrap everything with a single ScopedModel
widget, referring to the instance of our model class we created. Finally, for any child-widgets that need to access our state (even if they're in separate files), we simply wrap them in a ScopedModelDescendant
widget. Whatever is wrapped there can automagically react to our state updates. What I love about this solution is there's no complex architecture or large amount of boilerplate code we have to maintain to achieve this. Now let's implement something simple so that makes more sense. ## Terms We'll Be Throwing Around _ State: data. In our case, state changes over time as users interact with the UI. One or more "stateful" widgets may re-render as state changes. _ Model: a class that represents a "thing" in our app. Examples: User, Podcast, Episode, etc _ Scoped Model: a class that holds state variables and contains business logic _ Business Logic: code that affects data in our app, typically grouped by conern. Examples: the Business Logic that determines how we work with fetching and managing Podcasts in our app. _ Render: the act of drawing something on the screen, i.e. a Button, Image, etc. ## Let's Dive In Let's create a simple app to demonstrate scoped_model
. If you don't want to follow along, you can find the code here. Our app will be a pretty contrived example in order to keep things simple. It will: _ Show a screen with three simple text labels. _ When the plus button at the bottom is tapped, our state is updated. _ Because our labels are wired up to a Scoped Model, they'll get those updates automagically. ## Our Code Just a quick note on how we're organizing our app's code: _ main.dart
: loads our App
widget. _ app.dart
: a StatelessWidget
, rendering a MaterialApp
. _ /models/counter.dart
: a simple model that represents a Counter. _ /scoped_models/scoped_counters.dart
: our scoped model that contains state variables and state specific business logic. _ /screens/home/home.dart
: our main screen. _ /screens/home/widgets/widget1.dart
: a simple stateless widget that shows some hardcoded text with the latest state value appended to it. _ /screens/home/widgets/widget2.dart
: same as above, just different text and wiring up to another state variable. * /screens/home/widgets/widget3.dart
: same as above, just different text and wiring up to another state variable. ### Step 1: Flutter Create Let's make sure Flutter is up to date and generate a new project:
flutter upgrade
flutter create scoped_model_hello_world
Now, open your project in the IDE of your choice (I personally use VSCode) and replace main.dart
with the following: ## The Shell of Our App
// main.dart
import 'package:flutter/material.dart';
import 'app.dart';
void main() => runApp(App());
</pre>
```dart
// app.dart
import 'package:flutter/material.dart';
import 'screens/home/home.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
count
with a default value of 1
.// /models/counter.dart
class Counter {
int count = 1;
}
This is our (scoped) model. I called it a (scoped) model, even though as you'll see below there's an actual ScopedModel
widget, because we should separate our traditional models in /models
with scoped models, which define state variables and the business logic that relates to it. Note that our plain old Counter
model above may later have its own business logic but our scoped model, ScopedCounter
, exclusively includes state variables and business logic related to that state. We instantiate three Counter
objects. When the increment
method is triggered, we update each of those with a different value. After we update our state, we "notify" any widgets that rely on it via the notifyListeners()
as seen below. This will trigger any widgets that rely on this state to automatically update, exactly how your standard StatefulWidget
works:
// /scoped_models/scoped_counters.dart
import 'package:scoped_model/scoped_model.dart';
import '../models/counter.dart';
class ScopedCounter extends Model {
Counter counter1 = Counter();
Counter counter2 = Counter();
Counter counter3 = Counter();
increment() {
counter1.count += 1;
counter2.count += 5;
counter3.count += 10;
notifyListeners();
}
}
flutter create
command, just to ensure it's familiar to everyone. Here we render our three widgets on the screen along with a button that triggers an update to our state. The important widget to notice is is the ScopedModel
widget below, of type ScopedCounter
(the class we created above). We wrap our screen with this ScopedModel
widget, which will provide the functionality we need in each widget below. In other words, from the documentation: "If you need to pass a Model deep down your Widget hierarchy, you can wrap your Model in a ScopedModel Widget. This will make the Model available to all descendant Widgets."