What is Scoped Model in flutter

As the name suggests, the Scoped Model in Flutter examines the scope and passes data downwards. We can create the scope at the top first with a type. And, after that in the descendant widgets, we can pass that data type.

If you have already learned Provider, then you might sense the similarity. However, provider is more versatile and might be complex. Moreover, provider package uses ChangeNotifier of Flutter.

We’ll come to that point later.

Before that, let’s see what is Scoped Model, and how it works. In addition, whether we can use scoped model with other packages or not.

The best way to understand any topic is to view images first. Therefore, let’s view the Flutter application we’ve built using Scoped Model.

Scoped model example one
Scoped model example one

While we press the button, the state of the Text widget which display the number changes. As a result, the number increases.

The increment reflects also on the AppBar, and on the screen facing us.

Let’s see the next stage.

Scoped model example two
Scoped model example two

We’ve pressed the button five times, and the number changes in two places.

Now, we’re going to press the Next Page button to navigate to another page.

Why?

Because the Next Page is also another descendant of Scoped Model. Therefore, in that case, the number should increase there too.

Scoped model example three
Scoped model example three

Voila! It works.

The number in the Next Page changes simultaneously along with the home page.

Now, in this page, we will press the decrement button. As a result, the number will lower down. But, will that change the number in home page?

Let’s see. First, let’s press the decrement button.

Scoped model example four
Scoped model example four

We’ve pressed the decrement button twice and the number lowers down to 3.

Fine.

Next, we move back to the home page.

What do we see?

Scoped model example five
Scoped model example five

The number reduces to 3.

Consequently, we’ve successfully managed state with the help of Scoped Model.

To sum up, Scoped Model helps us to manage state in a very simple way.

Now, the time has come to inspect the code.

How do you use the scoped model in Flutter?

Firstly, we need to add the dependency.

dependencies:
  cupertino_icons: ^1.0.4
  flutter:
    sdk: flutter
  
  scoped_model: ^2.0.0-nullsafety.0

We’ve made it sure that the scoped_model package we’re using must adhere to the null safety.

Next, we should create a model class of Counter.

import 'package:scoped_model/scoped_model.dart';

class Counter extends Model {
  int _counter = 0;
  int get counter => _counter;
  void increment() {
    _counter++;
    notifyListeners();
  }

  void decrement() {
    _counter--;
    notifyListeners();
  }
}

Our Counter class extends the Model class provided by the package. As a result, the Counter object can notify all the components that subscribe to all the public properties and methods of Counter type.

Therefore, we need to declare the scope at the top and mention the type which is Counter here.

import 'package:flutter/material.dart';
import 'package:provider_et_sqflite/model/counter.dart';
import 'package:scoped_model/scoped_model.dart';

import 'my_home_page.dart';

class MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
    required this.counter,
  }) : super(key: key);
  final Counter counter;

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ScopedModel<Counter>(
      model: Counter(),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Scoped Model Simple',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),

        /// child widgets are now under its scope
        /// and we can use this model anywhere below
        ///
        home: const MyHomePage(),
      ),
    );
  }
}

As a result, we can pass the Counter object to all the descendants.

First see the Home page code.

import 'package:flutter/material.dart';
import 'package:provider_et_sqflite/model/counter.dart';
import 'package:scoped_model/scoped_model.dart';

import 'next_page.dart';

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

  static const String title = 'Number increased to';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: customAppBar(title),

      /// the child widget below can use the scoped model
      ///
      floatingActionButton: ScopedModelDescendant<Counter>(
        builder: (context, child, model) => FloatingActionButton.extended(
          onPressed: () {
            model.increment();
          },
          label: const Text(
            'Press to Increment',
            style: TextStyle(
              fontSize: 30,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ),

      /// the child widget below can use the scoped model
      ///
      body: ScopedModelDescendant<Counter>(
          builder: (context, child, model) => Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  const Text(
                    'Number increased to ...',
                    style: TextStyle(
                      fontSize: 30,
                      fontWeight: FontWeight.w600,
                      color: Colors.blueAccent,
                    ),
                  ),
                  Center(
                    child: Text(
                      model.counter.toString(),
                      style: const TextStyle(
                        fontSize: 100,
                        fontWeight: FontWeight.w600,
                        color: Colors.red,
                      ),
                    ),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => const NextPage(),
                        ),
                      );
                    },
                    child: const Text(
                      'Next Page',
                      style: TextStyle(
                        fontSize: 30,
                        fontWeight: FontWeight.w600,
                        color: Colors.red,
                      ),
                    ),
                  )
                ],
              )),
    );
  }

  AppBar customAppBar(String title) {
    return AppBar(
      centerTitle: true,
      //backgroundColor: Colors.grey[400],
      flexibleSpace: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [
              Colors.pink,
              Colors.grey,
            ],
            begin: Alignment.topRight,
            end: Alignment.bottomRight,
          ),
        ),
      ),
      //elevation: 20,
      titleSpacing: 80,
      leading: const Icon(Icons.menu),
      title: Text(
        title,
        textAlign: TextAlign.left,
      ),
      actions: [
        ScopedModelDescendant<Counter>(
          builder: (context, child, model) => Container(
            padding: const EdgeInsets.all(5),
            child: Text(
              model.counter.toString(),
              style: const TextStyle(
                fontSize: 30,
                fontWeight: FontWeight.w900,
                color: Colors.white,
              ),
            ),
          ),
        ),
        buildIcons(
          const Icon(
            Icons.navigate_next,
          ),
        ),
        buildIcons(
          const Icon(Icons.search),
        ),
      ],
    );
  }

  IconButton buildIcons(Icon icon) {
    return IconButton(
      onPressed: () {},
      icon: icon,
    );
  }
}

Now, as we go down the widget tree, we can use the Scoped Model Descendant with the type. So the descendant widgets can access that type with the help of model.

floatingActionButton: ScopedModelDescendant<Counter>(
        builder: (context, child, model) => FloatingActionButton.extended(
          onPressed: () {
            model.increment();
          },
....

In the same vein, the Next Page is the another descendant of Scoped Model. Consequently, at that page we can access all properties and methods of Counter class.

Moreover, the same state prevails across all the descendant widgets.

Let’s take a look at the Next Page code.

import 'package:flutter/material.dart';
import 'package:provider_et_sqflite/model/counter.dart';
import 'package:scoped_model/scoped_model.dart';

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

  static const String title = 'Next Page';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(title),
      ),

      /// the child widget below can use the scoped model
      ///
      floatingActionButton: ScopedModelDescendant<Counter>(
        builder: (context, child, model) => FloatingActionButton.extended(
          onPressed: () {
            model.decrement();
          },
          label: const Text(
            'Press to Decrement',
            style: TextStyle(
              fontSize: 30,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ),

      /// the child widget below can use the scoped model
      ///
      body: ScopedModelDescendant<Counter>(
          builder: (context, child, model) => Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  const Text(
                    'Number lowered to ...',
                    style: TextStyle(
                      fontSize: 30,
                      fontWeight: FontWeight.w600,
                      color: Colors.blueAccent,
                    ),
                  ),
                  Center(
                    child: Text(
                      model.counter.toString(),
                      style: const TextStyle(
                        fontSize: 100,
                        fontWeight: FontWeight.w600,
                        color: Colors.red,
                      ),
                    ),
                  ),
                ],
              )),
    );
  }

  AppBar customAppBar(String title) {
    return AppBar(
      centerTitle: true,
      //backgroundColor: Colors.grey[400],
      flexibleSpace: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [
              Colors.pink,
              Colors.grey,
            ],
            begin: Alignment.topRight,
            end: Alignment.bottomRight,
          ),
        ),
      ),
      //elevation: 20,
      titleSpacing: 80,
      leading: const Icon(Icons.menu),
      title: Text(
        title,
        textAlign: TextAlign.left,
      ),
      actions: [
        ScopedModelDescendant<Counter>(
          builder: (context, child, model) => Container(
            padding: const EdgeInsets.all(5),
            child: Text(
              model.counter.toString(),
              style: const TextStyle(
                fontSize: 30,
                fontWeight: FontWeight.w900,
                color: Colors.white,
              ),
            ),
          ),
        ),
        buildIcons(
          const Icon(
            Icons.navigate_next,
          ),
        ),
        buildIcons(
          const Icon(Icons.search),
        ),
      ],
    );
  }

  IconButton buildIcons(Icon icon) {
    return IconButton(
      onPressed: () {},
      icon: icon,
    );
  }
}

As a result, we’ve seen how we can manage the state across the whole flutter application.

Passing data across the Flutter Application

A Flutter User Interface can pass data all the way down the tree from parent to child. We can always pass them through constructors, or use Inherited widget.

But, doing that manually makes it cumbersome as our Flutter application gets bigger.

To solve this issue, we can use Scoped Model, which is a simplified version of Inherited widget. Of course, Provider works on the same principle, although having a lot more options.

Moreover, using Provider is not as easy as using Scoped Model. In both cases, we need to make a new Context.

Why?

Because the exposed data is included in the Context. Now any widget that uses that Context can access that data.

Now it’s always a good practice to place that access point as low as possible. In fact, we should use Scoped Model, or Provider as closest as possible to the widget that uses that exposed data.

The biggest advantage of using Scoped Model is it separates the User Interface and Business Logic. And that too in a very simple way.

To get the full code please visit the respective GitHub Repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

GitHub repository

Technical blog

Twitter

Comments

5 responses to “What is Scoped Model in flutter”

  1. […] the previous section we’ve seen how we can use Scoped Model in Flutter. In a couple of previous sections, we’ve also examined the scope of using SQLite Database in […]

  2. […] reason is, we can use FutureBuilder using Scoped Model or Provider. In other Words using the Inherited […]

  3. […] raison en est que nous pouvons utiliser FutureBuilder en utilisant Scoped Model ou Provider. En d’autres termes, en utilisant le Inherited […]

  4. […] reason is, we can use FutureBuilder using Scoped Model or Provider. In other Words using the Inherited […]

  5. […] raison en est que nous pouvons utiliser FutureBuilder en utilisant Scoped Model ou Provider. En d’autres termes, en utilisant le Inherited […]

Leave a Reply