Implementing a Scoped Model in your Flutter Application

December 10, 2020

In Flutter, everything is a widget. Developers compose high-quality and creative UI screens using widgets. The flexibility supports outstanding designs that impress users. These features are difficult to use in native app development using languages such as Java.

However, Flutter does pose specific challenges. Particularly, it can be difficult to implement state management. To learn about state management click here. It’s also challenging to pass data from the primary widget to its children. The scoped_model library helps resolve these issues.

Let’s dive in.

Introduction

The scoped_model library consists of various utilities that allow children UI elements to receive data from their parent Widget. The library consists of three major classes: the ScopedModelDescendant, the Model, and the ScopedModel.

You must extend the Model class to listen for changes.

The ScopeModel widget can be used to wrap UI components and access data from the Model. The ScopedModelDescendant lets you identify the correct ScopeModel from the widget hierarchy. You can learn more about the scoped_model library from here.

Let’s implement the library in our Flutter application.

Prerequisites

  • You must be familiar with Dart and Flutter

  • Have Android Studio, or Visual Studio Code installed.

  • Have the latest Flutter SDK.

  • You can download the full project from here.

The goal of the tutorial

By the end of this tutorial, you will create an application that follows state management principles in Flutter. It will allow customers to add and store notes. Plus the widgets will be updated as soon as data changes.

Creating the project

Open Android Studio click file, then create a new Flutter project. Ensure that you set the proper Flutter SDK path to avoid any errors. The computer also needs to be online for Flutter dependencies and libraries to be installed. If this process is successful, your start page should look like the image below.

You have to be patient since this stage takes time.

Creating new project

Installing the required library

You must install the required library for you to access the scoped_model’s functionalities. Open the pubspec.yaml file, go to the dependencies section, and paste scoped\_model : ^1.1.0. Ensure that the statement aligns vertically with the term flutter. Ignoring this detail will result in errors during compilation.

Your pubspec.yaml file should look as shown below.

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  scoped_model: ^1.1.0

Creating the UI

In this stage, we will use different widgets to create the user interface. Our final design should be similar to the image below.

App design

Modify ‘main.dart’ file

Go to the lib folder and open the main.dart file. You will notice that there is pre-generated code in the file. We won’t be needing some of this code, you can, therefore, delete the MyHomePage class since we will create a new one.

Then go to MyApp class, in the same file, and change the home parameter to home: MyHomePage(). You can follow the following code to avoid confusion.

import 'package:flutter/material.dart';
import './model/note.dart';
import './model/notesmodel.dart';
import 'package:scoped_model/scoped_model.dart';

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new ScopedModel<NotesModel>(
      model: notesModel,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

Design the homepage

Since we are making a simple application, we can include our UI code in the main.dart file. The class should be named as MyHomePage.

Paste the following code in the MyHomePage class.

import 'package:flutter/material.dart';
import './model/note.dart';
import './model/notesmodel.dart';
import 'package:scoped_model/scoped_model.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: ListView.builder(
          padding: EdgeInsets.all(5.0),
          itemCount: null, //calculating length ist
          itemBuilder: (BuildContext context, index) {
            return Column(
              children: <Widget>[
                ListTile(
                    leading: Image.network(
                      //fetching online images
                      "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Noto_Emoji_KitKat_263a.svg/1200px-Noto_Emoji_KitKat_263a.svg.png",
                      height: 20.0,
                      width: 20.0,
                    ),
                    title: Text("name"), //retrieving object's name from list

                    ),
                Divider()
              ],
            );
          }),

      floatingActionButton: FloatingActionButton(
        //objects will be added to the database when this button is clicked.
        onPressed: () {//this method is executed when the floating button is clicked
        },
        tooltip: 'Add to list new object',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

The MyHomePage class in the main.dart file contains three major widgets: ListTile, ListView, and a Floating Button. The ListTile will be used to display an object’s attributes.

All of the ListTiles will appear in the ListView. This means that the user will be able to scroll through the content.

Creating the Model

Please create a new package in the lib folder and name it as model. In this package, create a note.dart file. Use Note as your class name. Our program will have two major attributes or variables. These are the name and description.

class Note{
  String id, name, description;
  Note({ this.id, @required this.name, @required this.description});
}

Extending the ScopedModel

This scoped_model class will be responsible for state management in our Flutter application. The first step is to create a class named NotesModel and ensure that it extends the Model class. You should have import 'package:scoped_model/scoped_model.dart'; for the program to execute. The next step is to define the functions, variables, or data that will be required by widgets. The full code for the NotesModel is shown below.

import 'package:scoped_model/scoped_model.dart';
import 'note.dart';

class NotesModel extends Model{
  List<Note> _list = []; //list that stores Note objects

  List<Note> get list{ //returns a copy of list
    return [..._list];
  }
    void addNote(Note note){ //adds a Note object to list
        _list.add(note);
        notifyListeners();
    }

  void removeNote(Note note){
    _list.remove(note); //removes a Note object from list
    notifyListeners();
  }

_list will be used to store the Note objects.

The get function returns a copy of the Note list. The addNote() function adds objects in the _list. The removeNote() method allows a user to delete or remove objects from list. These methods are declared as void since they do not return anything.

Finishing up

Go to the main.dart file and initialize the NoteModel just before the MyApp class. Then go to MyHomePage.class file and wrap the MaterialApp widget with the ScopedModel. Ensure that you have inserted the correct Model, as shown below.

NotesModel notesModel = NotesModel();

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new ScopedModel<NotesModel>(
      model: notesModel,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

Next, go to the MyHomePage class file add the ScopedModelDescendant in the _HomePageState class as shown below. Note that the ScopedModelDescendant will take context, child, and model as parameters. Pass the model to the other UI functions, as shown below.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    Note task = Note(name: "Run", description: "Run a 40 mile marathon");
    return ScopedModelDescendant<NotesModel>(builder: (context, child, model) {
      //scoped model descendants helps to pass data to the children widgets.
      return Scaffold(
        appBar: AppBar(
          // Here, we take the value from the MyHomePage object that was created by
          // the App.build method, and use it to set our appbar title.
          title: Text(widget.title),
        ),
        body: ListView.builder(
            padding: EdgeInsets.all(5.0),
            itemCount: model.list.length, //calculating length ist
            itemBuilder: (BuildContext context, index) {
              return Column(
                children: <Widget>[
                  ListTile(
                      leading: Image.network(
                        //fetching online images
                        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Noto_Emoji_KitKat_263a.svg/1200px-Noto_Emoji_KitKat_263a.svg.png",
                        height: 20.0,
                        width: 20.0,
                      ),
                      title: Text(model.list[index]
                          .name) //retrieving object's name from list

                      ),
                  Divider()
                ],
              );
            }),

        floatingActionButton: FloatingActionButton(
          //objects will be added to the database when this button is clicked.
          onPressed: () {
            model.addNote(task); //this method is executed when the floating button is clicked
          },
          tooltip: 'Add to list new object',
          child: Icon(Icons.add),
        ), // This trailing comma makes auto-formatting nicer for build methods.
      );
    });
  }
}

Your final app should run, as shown in the video below.

Conclusion

The scoped_model simplifies the state management process. Data is passed from the parent to the children widgets quickly. The user is notified in case of any data changes. You can use the knowledge gained from this tutorial to create more productive and interactive Flutter applications.

References


Peer Review Contributions by: Peter Kayere


About the author

Michael Barasa

A lover of technology. An upright individual not afraid of getting out of the comfort zone and trying out new things.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.