Scoped Model and SQLite in Flutter

In 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 Flutter. In this section we’ll discuss how we can join Scoped Model and SQLite database, so that they work at tandem in Flutter.

We’re going to build a Note-keeper app where we’ll store Name and Location of users. Likewise, we’ve already built the same application with Provider and SQLite Database. If you have interest, please check it.

Firstly, we can always use SQLite database in Flutter. However, we need to use a special package or plugin sqflite which is available in pub.dev. We also need to use Future API, async, await keywords, and then functions to make it successful.

We’ve discussed this feature for absolute beginners in previous section, is Flutter single thread? If you’re a complete beginner searching to know about Future in Flutter, please check it.

First thing first, to use SQLite Database in Flutter, we use the sqflite package.

Why?

Because this package provides classes and functions to interact with a SQLite database. There are other reasons too, using SQLite database is better than using a local file, or key-value store.

In addition, SQLite database provides faster CRUD. That is, we can create, retrieve, update and delete data. And, it’s always better than any other local persistent solutions.

Besides sqflite package, we need to use another package path, that will define the location for storing the database on the disk.

For the beginners, here is a guide what SQLite database is.

What is SQLite database and how it works?

SQLite is a C-language library that implements many features at one go. It is smallfastself-containedhigh-reliabilityfull-featured, SQL database engine.

By the way, SQLite is the most used database engine in the world. Besides, SQLite databasefile format is stable, cross-platform, and backwards compatible.

There are over 1 trillion SQLite databases in active use at present.

Therefore, let’s go ahead and make our first Flutter Application with SQLite database.

Besides, using Scoped Model, and SQLite database, we’ll also use Future Builder widget. We’ll discuss Future Builder in detail later, meanwhile let’s learn a few key points about Future Builder.

Scoped Model, SQLite database and Future Builder

How about getting a gentle introduction to Future Builder?

Well, to understand the whole mechanism behind building a Note-keeper application in Flutter, this initiation might help us.

Future Builder is a widget that builds itself based on the latest snapshot of interaction with a Future.

According to the documentation we must obtain the future object during State.initStateState.didUpdateWidget, or State.didChangeDependencies.

However, while using with Provider or Scoped Model, we use the Future Builder in a different way. We’ll see that in a minute.

Next, let’s have another gentle introduction to “scoped_model” package also. Because without this very useful package, we couldn’t use Inherited Widget in the simplest way in Flutter.

How do you use scoped_model in flutter?

To start with we need to add all the dependencies first.

dependencies:
  cupertino_icons: ^1.0.4
  flutter:
    sdk: flutter
  path: ^1.8.0
  scoped_model: ^2.0.0-nullsafety.0
  sqflite: ^2.0.1

After that, with the help of Scoped Model we can easily pass a data model from a parent Widget to a child Widget.

Moreover, it also rebuilds all of the children that use the model when the model is updated.

The Scoped Model package provides three main classes.

Let’s see what they are.

Firstly, the Model class as we’ve used in the User Model, like the following.

import 'package:scoped_model/scoped_model.dart';

import 'database_handler.dart';
import 'user.dart';

class UserModel extends Model {
  User _userOne = User(name: 'Json Web', location: 'Detroit');
  User get userOne => _userOne;

  void addingUsers() {
    _userOne = userOne;

    notifyListeners();
  }
}

We need another User class that will define the behaviour of user object and map the items to give an output in a List of items.

class User {
  final int? id;
  final String name;
  final String location;

  User({
    this.id,
    required this.name,
    required this.location,
  });

  User.fromMap(Map<String, dynamic> res)
      : id = res["id"],
        name = res["name"],
        location = res["location"];

  Map<String, Object?> toMap() {
    return {
      'id': id,
      'name': name,
      'location': location,
    };
  }
}

As a result, we can construct User object inside User model class.

Secondly, we need two more widgets.

The first one is the Scoped Model Widget where we tell the parent widget to pass the Model class properties and methods to its descendant children widgets.

Since we need to pass a Model deep down your Widget hierarchy, you can wrap our Model in a ScopedModel Widget. This will make the Model available to all descendant Widgets.

import 'package:flutter/material.dart';

import 'package:provider_et_sqflite/model/user_model.dart';
import 'package:scoped_model/scoped_model.dart';

import 'my_home_page.dart';

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

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

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

As a result, the Home Page can now make the model available to all descendant widgets.

Finally, we need the Scoped Model Descendant Widget that can listen to the Models for any changes.

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

import '../model/user_model.dart';
import '/model/database_handler.dart';
import '/model/user.dart';

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

  static const String title = 'Database Handling';

  @override
  Widget build(BuildContext context) {
    final userModel = ScopedModel.of<UserModel>(context);

    final handler = DatabaseHandler();
    Future<int> addUsers() async {
      User firstUser = User(
        name: userModel.userOne.name,
        location: userModel.userOne.location,
      );
      List<User> listOfUsers = [
        firstUser,
      ];
      return await handler.insertUser(listOfUsers);
    }

    return ScopedModelDescendant<UserModel>(
      builder: (context, child, model) => Scaffold(
        appBar: customAppBar(title),
        body: FutureBuilder(
          future: handler.retrieveUsers(),
          builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                itemCount: snapshot.data?.length,
                itemBuilder: (BuildContext context, int index) {
                  return Card(
                    child: ListTile(
                      key: ValueKey<int>(snapshot.data![index].id!),
                      contentPadding: const EdgeInsets.all(8.0),
                      title: Text(
                        snapshot.data![index].name,
                        style: const TextStyle(
                          fontSize: 30,
                          color: Colors.red,
                        ),
                      ),
                      subtitle: Text(
                        snapshot.data![index].location,
                        style: const TextStyle(
                          fontSize: 20,
                          color: Colors.blue,
                        ),
                      ),
                    ),
                  );
                },
              );
            } else {
              return const Center(child: CircularProgressIndicator());
            }
          },
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () {
            handler.initializeDB().whenComplete(() async {
              await addUsers();
            });

            model.addingUsers();
          },
          label: const Text(
            'Add Users',
            style: TextStyle(
              fontSize: 25,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }

  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: [
        buildIcons(
          const Icon(Icons.add_a_photo),
        ),
        buildIcons(
          const Icon(
            Icons.notification_add,
          ),
        ),
        buildIcons(
          const Icon(
            Icons.settings,
          ),
        ),
        buildIcons(
          const Icon(Icons.search),
        ),
      ],
    );
  }

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

The Model has been passed down from the parent to the child Widget tree using an InheritedWidget. When an InheritedWidget is rebuilt, it will rebuild all of the Widgets that depend on its data.

As a result, when we press the “Add Users” button, one User with name and location is added to the SQLite Database. We’ve already defined the insert and retrieve methods in a Database helper class, like the following.

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

import 'user.dart';

class DatabaseHandler {
  Future<Database> initializeDB() async {
    String path = await getDatabasesPath();
    return openDatabase(
      join(path, 'userthirteen.db'),
      onCreate: (database, version) async {
        await database.execute(
          "CREATE TABLE userthirteen(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, location TEXT NOT NULL)",
        );
      },
      version: 1,
    );
  }

  Future<int> insertUser(List<User> users) async {
    int result = 0;
    final Database db = await initializeDB();
    for (var user in users) {
      result = await db.insert('userthirteen', user.toMap());
    }
    return result;
  }

  Future<List<User>> retrieveUsers() async {
    final Database db = await initializeDB();
    final List<Map<String, Object?>> queryResult =
        await db.query('userthirteen');
    return queryResult.map((e) => User.fromMap(e)).toList();
  }
}

Now, we need to add users through our User Model class, in two steps, like the following.

class UserModel extends Model {
  User _userOne = User(name: 'Json Web', location: 'Detroit');
  User get userOne => _userOne;
...
 Widget build(BuildContext context) {
    final userModel = ScopedModel.of<UserModel>(context);

    final handler = DatabaseHandler();
    Future<int> addUsers() async {
      User firstUser = User(
        name: userModel.userOne.name,
        location: userModel.userOne.location,
      );
      List<User> listOfUsers = [
        firstUser,
      ];
      return await handler.insertUser(listOfUsers);
...

Therefore, we get our first user added to our Name-keeper Flutter application as press the “Add Users” button.

Scoped Model and SQLite database example in Flutter
Scoped Model and SQLite database example in Flutter

Now we can keep added them through User Model class by pressing the button. However, we need to manually add them in User Model class which is not advisable.

Yet, to understand the mechanism it’s okay for the time being.

Later we’ll pass that Model class to the Scoped Model Descendant Widget to initialise SQLite Database handling process by taking user inputs.

So stay tuned.

For full code snippet please visit the respective GitHub Repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Technical blog

Twitter

Comments

3 responses to “Scoped Model and SQLite in Flutter”

  1. […] will use both Scoped Model and Provider to insert data to a SQLite database. As a result, the FutureBuilder widget will […]

  2. […] utiliserons à la fois Scoped Model et Provider pour insérer des données dans une SQLite database. En […]

  3. […] utiliserons à la fois Scoped Model et Provider pour insérer des données dans une SQLite database. En conséquence, le […]

Leave a Reply