Newby Coder header banner

Flutter Local Database

Local Sqlite Database in Android/iOS using Flutter

SQFlite is a plugin used in flutter to perform database operations for both Andoid and IOS apps

DB operations are executed in background on iOS and Android

sqflite automatically updates version at the time of open

Sqflite supports crud operations, transactions and batch operations

Creating new Flutter App

Check Flutter installation to setup Flutter

Use flutter create command to create a Flutter project (here local_db_app :

flutter create local_db_app

Dependency

Implementation

Import

Import the package into dart code

import ‘package:sqflite/sqflite.dart’;

Crud Operations

Example code to perform database CRUD operations:

// Get a location using getDatabasesPath
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, “demo.db”);
// Delete the database
await deleteDatabase(path);
// open the database
Database database = await openDatabase(path, version: 1,
    onCreate: (Database db, int version) async {
        // When creating the db, create the table
        await db.execute(
        “CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)”);
    });

// Insert some records in a transaction
await database.transaction((txn) async {
    int id1 = await txn.rawInsert(
    ‘INSERT INTO Test(name, value, num) VALUES(“some name”, 1234, 456.789)’);
    print(“inserted1: $id1”);
    int id2 = await txn.rawInsert(
    ‘INSERT INTO Test(name, value, num) VALUES(?, ?, ?)’,
    [“another name”, 12345678, 3.1416]);
    print(“inserted2: $id2”);
});
// Update some record
int count = await database.rawUpdate(
    ‘UPDATE Test SET name = ?, VALUE = ? WHERE name = ?’,
    [“updated name”, “9876”, “some name”]);
    print(“updated: $count”);
// Get the records
List<Map> list = await database.rawQuery(‘SELECT * FROM Test’);


// Count the records
count = Sqflite.firstIntValue(await database.rawQuery(“SELECT COUNT(*) FROM Test”));
// Delete a record
count = await database.rawDelete(‘DELETE FROM Test WHERE name = ?’, [‘another name’]);
// Close the database
await database.close();

Transaction

Use the Transaction object in a transaction to access the database

await database.transaction((txn) async {
    await txn.execute(“CREATE TABLE Test1 (id INTEGER PRIMARY KEY)”);

    // DON’T use the database object in a transaction to avoid deadlock
    await database.execute(“CREATE TABLE Test2 (id INTEGER PRIMARY KEY)”);
});

App Code

This example app code displays and allows to create and delete contacts which are stored in database

Data structure for a Contact and conversions to and from String are declared in contact_model.dart

local_db_app/lib/contact_model.dart
import 'dart:convert';

Contact contactFromJson(String str) {
  final jsonData = json.decode(str);
  return Contact.fromMap(jsonData);
}

String contactToJson(Contact data) {
  final dyn = data.toMap();
  return json.encode(dyn);
}

class Contact {
  int id;
  String name;
  String number;

  Contact({
    this.id,
    this.name,
    this.number,
  });

  factory Contact.fromMap(Map<String, dynamic> json) => new Contact(
        id: json["id"],
        name: json["name"],
        number: json["number"],
      );

  Map<String, dynamic> toMap() => {
        "id": id,
        "name": name,
        "number": number,
      };
}

database.dart declares class DBProvider which contains functions to interact with database

local_db_app/lib/database.dart
import 'dart:async';
import 'dart:io';

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


class DBProvider {
  DBProvider._();
  static final DBProvider db = DBProvider._();
  Database _database;

  Future<Database> get database async {
    if (_database != null) return _database;
    // if _database is null, it is instantiated
    _database = await initDB();
    return _database;
  }

  initDB() async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String path = join(documentsDirectory.path, "TestDB.db");
    return await openDatabase(path, version: 1, onOpen: (db) {},
        onCreate: (Database db, int version) async {
      await db.execute("CREATE TABLE Contact ("
          "id INTEGER PRIMARY KEY,"
          "name TEXT,"
          "number TEXT,"
          ")");
    });
  }

  insertContact(Contact newContact) async {
    final db = await database;
    //get max id of table and increment to get id for insertion
    var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM Contact");
    int id = table.first["id"];
    var raw = await db.rawInsert(
        "INSERT Into Contact (id,name,number)"
        " VALUES (?,?,?)",
        [id, newContact.name, newContact.number]);
    return raw;
  }

  updateContact(Contact newContact) async {
    final db = await database;
    var res = await db.update("Contact", newContact.toMap(),
        where: "id = ?", whereArgs: [newContact.id]);
    return res;
  }

  getContact(int id) async {
    final db = await database;
    var res = await db.query("Contact", where: "id = ?", whereArgs: [id]);
    return res.isNotEmpty ? Contact.fromMap(res.first) : null;
  }

  Future<List<Contact>> getAllContacts() async {
    final db = await database;
    var res = await db.query("Contact");
    List<Contact> list =
        res.isNotEmpty ? res.map((c) => Contact.fromMap(c)).toList() : [];
    return list;
  }

  deleteContact(int id) async {
    final db = await database;
    return db.delete("Contact", where: "id = ?", whereArgs: [id]);
  }

  deleteAll() async {
    final db = await database;
    db.rawDelete("Delete * from Contact");
  }
}

Main app code with a stateful widget to display contacts present in db which also allows to add and remove contacts

local_db_app/lib/main.dart
import 'package:flutter/material.dart';
import 'contact_model.dart';
import 'database.dart';
import 'dart:math' as math;
import 'dart:async';


void main() => runApp(MaterialApp(home: NcSqliteApp()));

class NcSqliteApp extends StatefulWidget {
  @override
  _NcSqliteAppState createState() => _NcSqliteAppState();
}

class _NcSqliteAppState extends State<NcSqliteApp> {
  bool isEdit = false;
  List<Contact> testContacts = [
    Contact(name: "Takzia", number: "8941722312"),
    Contact(name: "Grazi", number: "8217391734"),
    Contact(name: "Hyung", number: "8721461846"),
    Contact(name: "Abibeide", number: "9326724233"),
  ];

  Future _openAddUserDialog(isEdit, id) async {
    Contact contact = null;
    if( id!=null ) {
     contact =  await DBProvider.db.getContact(id);
    }
    showDialog(
      context: context,
      builder: (BuildContext context) =>
          new AddUserDialog().buildAboutDialog(context, this, isEdit, contact),
    );
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // to avoid overflow during keyboard display
      resizeToAvoidBottomInset: false,
      resizeToAvoidBottomPadding: false,
      appBar: AppBar(title: Text("Flutter SQLite Example")),
      body: Column(
        children: [
          Container(
            height:400,
            child: FutureBuilder<List<Contact>>(
              future: DBProvider.db.getAllContacts(),
              builder: (BuildContext context, AsyncSnapshot<List<Contact>> snapshot) {
                if (snapshot.hasData) {
                  return ListView.builder(
                    shrinkWrap: true,
                    physics: const AlwaysScrollableScrollPhysics(),
                    itemCount: snapshot.data.length,
                    itemBuilder: (BuildContext context, int index) {
                      Contact item = snapshot.data[index];
                      return Dismissible(
                        key: UniqueKey(),
                        background: Container(color: Colors.red),
                        onDismissed: (direction) {
                          DBProvider.db.deleteContact(item.id);
                        },
                        child: ListTile(
                          title: Text("${item.name}    ${item.number}"),
                          leading: Text(item.id.toString()),
                          trailing: Transform.scale(
                            scale: 0.5,
                            child: IconButton(
                              onPressed: (){
                                _openAddUserDialog(true, item.id);
                              },
                              icon: Icon(Icons.edit),
                            ),
                          ),
                        ),
                      );
                    },
                  );
                }
                else {
                  return Center(child: CircularProgressIndicator());
                }
              },
            ),
          ),
          new GestureDetector(
            onTap: () {
              _openAddUserDialog(false, null);
            },
            child: new Container(
              margin: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
              child: getBorderButton(
                  "Add", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0)
              ),
            ),
          ),

        ],
      ),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () async {
          Contact rnd = testContacts[math.Random().nextInt(testContacts.length)];
          await DBProvider.db.insertContact(rnd);
          setState(() {});
        },
      ),
    );
  }
}

Widget getBorderButton(String buttonLabel, EdgeInsets margin) {
  return new Container(
    margin: margin,
    padding: EdgeInsets.all(8.0),
    alignment: Alignment.center,
    decoration: new BoxDecoration(
      border: Border.all(color: const Color(0xFF28324E)),
      borderRadius: new BorderRadius.all(const Radius.circular(6.0)),
    ),
    child: new Text(
      buttonLabel,
      style: new TextStyle(
        color: const Color(0xFF28324E),
        fontSize: 20.0,
        fontWeight: FontWeight.w300,
        letterSpacing: 0.3,
      ),
    ),
  );
}

class AddUserDialog {
  final teFirstName = TextEditingController();
  final teNumber = TextEditingController();
  Contact contact;

  static const TextStyle linkStyle = const TextStyle(
    color: Colors.blue,
    decoration: TextDecoration.underline,
  );

  Widget buildAboutDialog(BuildContext context, _NcSqliteAppState appstate, bool isEdit, Contact contact) {
    if (contact != null) {
      this.contact=contact;
      teFirstName.text = contact.name;
      teNumber.text = contact.number;
    }

    return new AlertDialog(
      title: new Text(isEdit ? 'Edit' : 'Add new User'),
      content: new SingleChildScrollView(
        child: new Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            getTextField("Enter name", teFirstName),
            getTextField("Enter number", teNumber),
            new GestureDetector(
              onTap: () {
                addRecord(isEdit);
                appstate.setState(() {});
                Navigator.of(context).pop();
              },
              child: new Container(
                margin: EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 0.0),
                child: getBorderButton(
                    isEdit?"Update":"Add", EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0)),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget getTextField(
    String inputBoxName, TextEditingController inputBoxController) {
    return new Padding(
      padding: const EdgeInsets.all(5.0),
      child: new TextFormField(
        controller: inputBoxController,
        decoration: new InputDecoration(
          hintText: inputBoxName,
        ),
      ),
    );
  }

  Future addRecord(bool isEdit) async {
    Contact newContact = Contact(name: teFirstName.text, number: teNumber.text);
    if(!isEdit)
      await DBProvider.db.insertContact(newContact);
    else {
      newContact.id = contact.id;
      await DBProvider.db.updateContact(newContact);
    }
  }
}

Run instructions

Ensure a supported device is connected or emulator/simulator is started

Go to project directory

Use flutter run command to run

flutter run

It builds and runs app on an available android/ios device


Screenshot/image

Android

cl-flutter-local-database

iOS

cm-flutter-local-database-as1cm-flutter-local-database-as2cm-flutter-local-database-as3
cm-flutter-local-database-as4cm_flutter_local_database_as5