SQLite/SQFlite CRUD Database Operations in Flutter

Jamie
6 min readOct 9, 2020

--

Watch Video Tutorial

SQLite CRUD Operations in Flutter

SQLite is a local database for storing structured data, so it can be called as a mini database. Most of the time we use SQLite Databases in Mobile Applications. To use SQLite in Flutter, we have an awesome plugin called SQFlite. To start with we need to add Dependency to our project’s pubspec.yaml file

Add Dependency

You can get it from here.

https://pub.dev/packages/sqflite

To Integrate SQFlite library in your project, Open the pubspec.yaml file…

dependencies:
// your other dependencies goes here
...
sqflite: any

Let’s create a simple database of employees

Create a new file named ‘employee.dart’ and the contents will look like this

class Employee {
int id;
String name;

Employee(this.id, this.name);

Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'id': id,
'name': name,
};
return map;
}

Employee.fromMap(Map<String, dynamic> map) {
id = map['id'];
name = map['name'];
}
}

Here each employee will have an ‘id’ and ‘name’.

Database Utils

We will write a database utility file where we will write our database utility functions.

Create a file named “DBHelper.dart” and copy the below code into it.

import 'dart:async';
import 'dart:io' as io;
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'employee.dart';

class DBHelper {
static Database _db;
static const String ID = 'id';
static const String NAME = 'name';
static const String TABLE = 'Employee';
static const String DB_NAME = 'employee1.db';

Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}

initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, DB_NAME);
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}

_onCreate(Database db, int version) async {
await db
.execute("CREATE TABLE $TABLE ($ID INTEGER PRIMARY KEY, $NAME TEXT)");
}

Future<Employee> save(Employee employee) async {
var dbClient = await db;
employee.id = await dbClient.insert(TABLE, employee.toMap());
return employee;
/*
await dbClient.transaction((txn) async {
var query = "INSERT INTO $TABLE ($NAME) VALUES ('" + employee.name + "')";
return await txn.rawInsert(query);
});
*/
}

Future<List<Employee>> getEmployees() async {
var dbClient = await db;
List<Map> maps = await dbClient.query(TABLE, columns: [ID, NAME]);
//List<Map> maps = await dbClient.rawQuery("SELECT * FROM $TABLE");
List<Employee> employees = [];
if (maps.length > 0) {
for (int i = 0; i < maps.length; i++) {
employees.add(Employee.fromMap(maps[i]));
}
}
return employees;
}

Future<int> delete(int id) async {
var dbClient = await db;
return await dbClient.delete(TABLE, where: '$ID = ?', whereArgs: [id]);
}

Future<int> update(Employee employee) async {
var dbClient = await db;
return await dbClient.update(TABLE, employee.toMap(),
where: '$ID = ?', whereArgs: [employee.id]);
}

Future close() async {
var dbClient = await db;
dbClient.close();
}
}

Let’s see what the above code does…

initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, DB_NAME);
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}

the initDb() function will create a database file in the App’s Documents directory

Future<Employee> save(Employee employee) async {
var dbClient = await db;
employee.id = await dbClient.insert(TABLE, employee.toMap());
return employee;
/*
await dbClient.transaction((txn) async {
var query = "INSERT INTO $TABLE ($NAME) VALUES ('" + employee.name + "')";
return await txn.rawInsert(query);
});
*/
}

The save function will insert the employee data into the table in the database.

There are two ways to write queries.

await dbClient.transaction((txn) async {
var query = "INSERT INTO $TABLE ($NAME) VALUES ('" + employee.name + "')";
return await txn.rawInsert(query);
});

if you want to write the raw query, you can use the above method.

Select

Future<List<Employee>> getEmployees() async {
var dbClient = await db;
List<Map> maps = await dbClient.query(TABLE, columns: [ID, NAME]);
//List<Map> maps = await dbClient.rawQuery("SELECT * FROM $TABLE");
List<Employee> employees = [];
if (maps.length > 0) {
for (int i = 0; i < maps.length; i++) {
employees.add(Employee.fromMap(maps[i]));
}
}
return employees;
}

Update

Future<int> update(Employee employee) async {
var dbClient = await db;
return await dbClient.update(TABLE, employee.toMap(),
where: '$ID = ?', whereArgs: [employee.id]);
}

Delete

Future<int> delete(int id) async {
var dbClient = await db;
return await dbClient.delete(TABLE, where: '$ID = ?', whereArgs: [id]);
}

Now we will write a Screen where we will implement the above functions

import 'package:flutter/material.dart';
import 'employee.dart';
import 'dart:async';
import 'db_helper.dart';

class DBTestPage extends StatefulWidget {
final String title;

DBTestPage({Key key, this.title}) : super(key: key);

@override
State<StatefulWidget> createState() {
return _DBTestPageState();
}
}

class _DBTestPageState extends State<DBTestPage> {
//
Future<List<Employee>> employees;
TextEditingController controller = TextEditingController();
String name;
int curUserId;

final formKey = new GlobalKey<FormState>();
var dbHelper;
bool isUpdating;

@override
void initState() {
super.initState();
dbHelper = DBHelper();
isUpdating = false;
refreshList();
}

refreshList() {
setState(() {
employees = dbHelper.getEmployees();
});
}

clearName() {
controller.text = '';
}

validate() {
if (formKey.currentState.validate()) {
formKey.currentState.save();
if (isUpdating) {
Employee e = Employee(curUserId, name);
dbHelper.update(e);
setState(() {
isUpdating = false;
});
} else {
Employee e = Employee(null, name);
dbHelper.save(e);
}
clearName();
refreshList();
}
}

form() {
return Form(
key: formKey,
child: Padding(
padding: EdgeInsets.all(15.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
verticalDirection: VerticalDirection.down,
children: <Widget>[
TextFormField(
controller: controller,
keyboardType: TextInputType.text,
decoration: InputDecoration(labelText: 'Name'),
validator: (val) => val.length == 0 ? 'Enter Name' : null,
onSaved: (val) => name = val,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
onPressed: validate,
child: Text(isUpdating ? 'UPDATE' : 'ADD'),
),
FlatButton(
onPressed: () {
setState(() {
isUpdating = false;
});
clearName();
},
child: Text('CANCEL'),
)
],
),
],
),
),
);
}

SingleChildScrollView dataTable(List<Employee> employees) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: [
DataColumn(
label: Text('NAME'),
),
DataColumn(
label: Text('DELETE'),
)
],
rows: employees
.map(
(employee) => DataRow(cells: [
DataCell(
Text(employee.name),
onTap: () {
setState(() {
isUpdating = true;
curUserId = employee.id;
});
controller.text = employee.name;
},
),
DataCell(IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbHelper.delete(employee.id);
refreshList();
},
)),
]),
)
.toList(),
),
);
}

list() {
return Expanded(
child: FutureBuilder(
future: employees,
builder: (context, snapshot) {
if (snapshot.hasData) {
return dataTable(snapshot.data);
}

if (null == snapshot.data || snapshot.data.length == 0) {
return Text("No Data Found");
}

return CircularProgressIndicator();
},
),
);
}

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Flutter SQLITE CRUD DEMO'),
),
body: new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
verticalDirection: VerticalDirection.down,
children: <Widget>[
form(),
list(),
],
),
),
);
}
}

Let’s see what the above code does…

The ‘form()’ function will create a Form widget with a TextField.

validator: (val) => val.length == 0 ? 'Enter Name' : null,
onSaved: (val) => name = val,

The above code will check if the textfield is empty or null and if it returns true, the validate() function will be triggered assigning the name to the value from the TextField.

We are setting a boolean isUpdating to true if we are updating and employee.

The below code fetches the employees and refreshes the list.

refreshList() {
setState(() {
employees = dbHelper.getEmployees();
});
}

Then we update the DataTable in the UI.

SingleChildScrollView dataTable(List<Employee> employees) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: [
DataColumn(
label: Text('NAME'),
),
DataColumn(
label: Text('DELETE'),
)
],
rows: employees
.map(
(employee) => DataRow(cells: [
DataCell(
Text(employee.name),
onTap: () {
setState(() {
isUpdating = true;
curUserId = employee.id;
});
controller.text = employee.name;
},
),
DataCell(IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbHelper.delete(employee.id);
refreshList();
},
)),
]),
)
.toList(),
),
);
}

Each row in the DataTable is made up of DataCell

DataCell(
Text(employee.name),
onTap: () {
setState(() {
isUpdating = true;
curUserId = employee.id;
});
controller.text = employee.name;
},
),

The second column in the DataTable will have an IconButton with a delete button.

DataCell(
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbHelper.delete(employee.id);
refreshList();
},
),
),

onPress of the delete button, we will call the delete function from the dbHelper class and refresh the list from the database.

employees.map((employee)
....

The above code will map the list of employees in the DataTable.

That’s it. We have successfully, fetched, inserted, updated and deleted employee records from the SQLite Database.

Please leave your valuable comments below.

Thanks for reading.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jamie
Jamie

Written by Jamie

Flutter, React Native, Android, iOS App developer.

No responses yet

Write a response