SQLite with Provider in Flutter

Can we use SQLite database with Provider package in Flutter? The answer is, yes! We can.

Not only that, we can also reduce pressure on system resource while we store persistent data with provider package.

Most importantly, we always want to make our Flutter app faster and performant. Since storing persistent data requires a lot of state management, the Provider package always helps us to achieve that target.

As a result, in this section, we try to use SQLite database with Provider.

No stateful widget. No extra widget rebuilding. We’ve kept things quite simple. However, if you are a beginner, please learn Future, await and async first. As we’ve discussed Future, await and async for absolute beginners in previous section- is Flutter single thread? Therefore, if you’re a beginner, you might take a look before we proceed towards the final section.

Before using provider package, we’ve built an entire Blog, or My Diary application using SQLite Database in Flutter.

Not only that, before doing that, in a step by step process we’ve built the SQLite Blog Application in Flutter. We might also see the progress of the initial phase in this section – SQLite Blog application in Flutter.

The previous section has discussed the application structure.

In the second part, we’d concentrated on database connectiondata model classes.

Firstly, we’ll use a Flutter package or plugin, sqflite which is available in pub.dev.

Secondly, we also need to use Future API, async, await keywords, and then functions to make it successful.

And finally, we are going to store persistent data in our local SQLite database, using provider package.

What is Sqflite flutter?

The sqflite is a very useful SQLite plugin for Flutter. It supports iOS, Android and MacOS.

For any type of complex CRUD operations, we get support from this plugin, or package. Moreover, this plugin supports transactions and batches.

Therefore, we have helpers for insert, query, update and delete queries. Above all, the DB operation executed in a background thread on iOS and Android. As a result, we get a much faster Flutter application than any other backend operation.

We need to add the dependencies first to our pubspec.yaml file.

dependencies:
  cupertino_icons: ^1.0.2
  flutter:
    sdk: flutter
  path: ^1.8.0
  provider: ^6.0.2
  sqflite: ^2.0.1

The three packages in bold, are necessary to build our first Name-Keeper Flutter Application using SQLite database and Provider.

How do I get data from SQLite database in flutter?

Our next challenge is to create a helper class. It will not only create the SQLite database in a given path, but also create the table. Moreover, it will insert and retrieve data.

Besides the helper class, we need a User data model, and a User Provider class that will notify the listeners.

We’ll take a look at the classes separately and try to understand how they work together. We have kept three classes in our model folder.

Firstly, the helper class.

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, 'usereleven.db'),
      onCreate: (database, version) async {
        await database.execute(
          "CREATE TABLE usereleven(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('usereleven', user.toMap());
    }
    return result;
  }

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

The above code is quite verbose and meaningful. The database handler class will first define a path where SQLite database gets created.

After that, it will create a table with ID auto increment, and two columns where we store the name and location.

Finally, it defines two methods to insert and retrieve data from the local database.

However, the Future object wants a list of Users that it can map to list so that we can finally get them on screen after the insertion is over.

Therefore, let’s take a look at the User class, next.

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,
    };
  }
}

Now we need a user provider class that will notify the listeners when we press the button “Add Users” like the following screenshot.

SQLite database and Provider in Flutter first screen
SQLite database and Provider in Flutter first screen

At the same time, we’ll also keep the Provider at the top of the root widget.

import 'package:flutter/material.dart';
import '/model/user_provider.dart';
import 'view/my_app.dart';

void main() {
  Provider.debugCheckInvalidValueType = null;
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserProvider()),
      ],
      child: const MyApp(),
    ),
  );
}

The User Provider class plays the most important role in this SQLite database and Flutter application.

import 'package:flutter/material.dart';

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

final handler = DatabaseHandler();

class UserProvider with ChangeNotifier {
  User _userOne = User(name: 'Hagudu', location: 'Japan');
  User get userOne => _userOne; 
  /*  
User _userTwo = User(name: 'Mutudu', location: 'Hokkaidu');
  User get userTwo => _userTwo; 

  User _userThree = User(name: 'John Smith', location: 'East Coast');
  User get userThree => _userThree;
*/

  void addingUsers() {
    _userOne = _userOne;
    //_userTwo = userTwo;
    //_userThree = userThree;

    notifyListeners();
  }
}

We’ve commented out other users as we want to insert one user at a time. We could have added them at once of course.

Is SQLite persistent?

We’re going to see how the SQLite database persistently stores data, so we can keep adding one user name and location one after another.

The Change Notifier Provider works with Future builder here, in the my home page widget.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/model/user_provider.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 userProvider = Provider.of<UserProvider>(context);

    final handler = DatabaseHandler();
    Future<int> addUsers() async {
     User firstUser = User(
        name: userProvider.userOne.name,
        location: userProvider.userOne.location,
      );

      /* 
User secondUser = User(
        name: userProvider.userTwo.name,
        location: userProvider.userTwo.location,
      ); 

      User thirddUser = User(
        name: userProvider.userThree.name,
        location: userProvider.userThree.location,
      );
*/
      List<User> listOfUsers = [
        firstUser,
        //secondUser,
        //thirddUser,
      ];
      return await handler.insertUser(listOfUsers);
    }

    return 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.red,
                      ),
                    ),
                  ),
                );
              },
            );
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          handler.initializeDB().whenComplete(() async {
            await addUsers();
          });

          userProvider.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 flow of logic is quite simple. Inside our build method, we’ve got the Provider of Type User Provider and its context.

Next, we have instantiated the Database handler object. Without this handler we cannot initiate the process of inserting and retrieving data.

As a result, we can press the “Add Users” button that fires the event of inserting and retrieving data from the SQLite database.

SQLite database and Provider in Flutter with first user
SQLite database and Provider in Flutter with first user

Once we have inserted the first user name and location, we can comment out the first user in User and User Handler class.

Then we can insert the second user’s name and location.

SQLite database and Provider in Flutter with second user
SQLite database and Provider in Flutter with second user

We can clearly see that how SQLite database persists data. However, we don’t have to use stateful widget to manage state. The provider package helps us to notify listeners which is a Future builder.

Now, we can add as many user’s name and location.

SQLite database and Provider in Flutter with third user
SQLite database and Provider in Flutter with third user

For the 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

2 responses to “SQLite with Provider in Flutter”

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

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

Leave a Reply