A detailed explanation of storage used in a flutter

Pravin Palkonda
12 min readJan 11, 2023

--

While working with the application, we may have to store a small amount of data locally or in a cloud. The data may be like user data or any data related to the application. Flutter provides a package that allows us to save data locally. We just need to add these dependencies and are ready to start working with them.

Shared preferences

Shared preference is the flutter favorite and most commonly used package which is used to store data locally in key-value pairs. By using this package we can store, read, update and delete data locally. Shared preferences are used only to store a small amount of data. It is not recommended to store a large amount of data.

Go to pub.dev and search for shared preferences. Check for the latest version and add it into pubspec.yaml file.

pubspec.yaml file

By using the shared preference we can only save the data like String, StringList, int, double, and bool.

How to save the data

Create the instance of shared preference.

Shared preference provides a setter method to save the value with primitive types such as setInt, setString, and setBool.

The Setter method provides two properties such as key and value. Here we can define the key names only in a string and it must be unique.

/// instance of shared preference
final prefs = await SharedPreferences.getInstance();

/// setting a value
/// here we set a value in key value pair
await prefs.setString('key-name', value);

How to read a data

Shared preference provides a getter method that is used to get a value from the key name. Each setter has a getter such as getInt, getBool, and getString.

final prefs = await SharedPreferences.getInstance();

/// here we are reading a value from the key name
/// here we should aware the value we are reading should not be null
/// so better use null safety
final data= prefs.getString('key-name') ?? 'No data';

How to update the data

To update the data we just need to re-assign the new value to the key.


final prefs = await SharedPreferences.getInstance();

/// setting a value
/// here we set a value in key value pair
await prefs.setString('key-name', value);

/// to update just re-assign the value
await prefs.setString('key-name', new-value);

How to delete data

To delete data, shared preference provides us remove() method. We just need to pass the key name which is need to be deleted.

final prefs = await SharedPreferences.getInstance();

/// to remove the data using key name
await prefs.remove('key-name');

Let's create a screen with a text field and buttons. On this screen, we will create one text field where the user can enter the text to save it locally. Then we will create buttons to save, update and delete the stored data.

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

class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
TextEditingController controller = TextEditingController();


@override
void initState() {
// TODO: implement initState
super.initState();

}


@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home page"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Display the stored data here..."),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: TextField(
controller: controller,
decoration: InputDecoration(
hintText: "Enter your data here",
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.green),
borderRadius: BorderRadius.circular(16.0)),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.green),
borderRadius: BorderRadius.circular(16.0))),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
),
ElevatedButton(
onPressed: (){},
child: const Text("Save the data")),
ElevatedButton(
onPressed: (){},
child: const Text("Update the data")),
ElevatedButton(
onPressed: (){},
child: const Text("Delete the data"))
],
),
),
);
}
}

Let's create a function to save the data, read the data, delete the data and update the data.



class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
TextEditingController controller = TextEditingController();

/// variable to hold the data
String? storedData;

@override
void initState() {
// TODO: implement initState
super.initState();

/// read the data as page gets loaded
getStoredData();
}

getStoredData() async {
final prefs = await SharedPreferences.getInstance();

/// to read the value from key name and to store it in variable, if it is null show another string
storedData =
prefs.getString('stored-data') ?? "Display the stored data here...";

/// to change the state when function is called
setState(() {});
}

saveAndUpdateStoredData() async {
final prefs = await SharedPreferences.getInstance();

/// to save the value using key name as stored-data
/// checking the condition if controller is not empty else empty value will be set
if (controller.text.isNotEmpty) {
/// passing the controller value to store the data in preferences
prefs.setString('stored-data', controller.text.toString());
}

///again calling this function to display the updated and saved data
getStoredData();
setState(() {});
}

deleteStoredData() async {
final prefs = await SharedPreferences.getInstance();

/// to delete the data using key-name as stored-data
prefs.remove('stored-data');

/// calling this function to update the deleted value
getStoredData();
setState(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home page"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
/// displaying the value which is stored using shared preference
Text(
storedData.toString(),
style:
const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: TextField(
controller: controller,
decoration: InputDecoration(
hintText: "Enter your data here",
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.green),
borderRadius: BorderRadius.circular(16.0)),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.green),
borderRadius: BorderRadius.circular(16.0))),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
),
ElevatedButton(
onPressed:
saveAndUpdateStoredData, // calling the function to save the data
child: const Text("Save the data")),
ElevatedButton(
onPressed:
saveAndUpdateStoredData, // calling the function to update the data
child: const Text("Update the data")),
ElevatedButton(
onPressed:
deleteStoredData, // calling the function to delete the data
child: const Text("Delete the data"))
],
),
),
);
}
}

Find the source code for shared preference here.

By following the above example, we can also save the data like double, int, StringList, and bool.

SQFlite

Sqflite is another flutter favorite and the most used package to store the data locally for the application. By using this package, we can perform all kind of database operation such as create, read, update and delete which is known as CRUD operations. This package allows us to create a database locally.

To work with sqflite, firstly we need to add dependency in pubspec.yaml file.

Go to pub.dev, search for the sqflite. Check the latest version and add it to the pubspec.yaml file. We also need to add a path package which is to get the path of the database that exists which is created locally in the application.

It will be easy to understand sqflite by using examples. Let's work on an example where we will add user data and display it as a list on a page.

Add dependencies

pubspec.yaml file

Before adding any data to the database we have to check whether the database is there or not. If not then create a new database for which we have to provide the database name and version of the database.

Here we will create a separate file that will handle all the database-related queries.

main.dart 

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


void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQFlite Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}

home page where the all the data of user will be displayed in a list.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:sqflite_demo/add_edit_user_data.dart';

import 'local_database.dart';
import 'user_data.dart';

class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
/// create list to add the user data
List<UserData> allUserDataList = [];

/// instance of the local database
final localDatabase = LocalDatabase.instance;
@override
void initState() {
super.initState();

/// to get the user data when page gets loaded if data exist
initialData();
}

initialData() async {
/// calling function to get user data which is create in local database file
final allData = await localDatabase.getAllUserData();
allUserDataList.clear();
for (var i in allData) {
/// adding value in the list
allUserDataList.add(UserData.fromMap(i));
}

/// to reload the page
setState(() {});
}

/// to delete the user by id
_delete(id) async {
await localDatabase.deleteUserData(id);
initialData();
setState(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("User list"),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
/// to navigate to the page where user add details
/// catch the boolean response if data is added
bool? isAdded = await Navigator.push(context,
MaterialPageRoute(builder: (_) => const AddEditUserData()));

/// if data is added calling initialData function again which will reflect the changes rapidly
if (isAdded == true) {
initialData();
setState(() {});
}
},
child: const Icon(Icons.add)),
body: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 10.0),
separatorBuilder: (context, index) => const Divider(),
shrinkWrap: true,
itemCount: allUserDataList.length,
itemBuilder: ((context, index) {
return ListTile(
onTap: () async {
/// navigate to edit the data
/// catching the boolean response for edit
bool? isUpdated = await Navigator.push(
context,
CupertinoPageRoute(
builder: (_) => AddEditUserData(
isEdit: true,
userData: allUserDataList[index],
)));

/// if data is updated again calling initialData function
/// to reflect the changes rapidly
if (isUpdated == true) {
initialData();
setState(() {});
}
},

/// displaying first name and last name
title: Text(
" ${allUserDataList[index].firstName} ${allUserDataList[index].lastName}"),

/// displaying mobile number and age
subtitle: Text(
"Mobile : ${allUserDataList[index].mobile} Age : ${allUserDataList[index].age}"),

/// displaying the id
leading: CircleAvatar(
child: Text(" ${allUserDataList[index].id}"),
),

/// button to delete the record
trailing: IconButton(
onPressed: () {
/// calling delete function which requires id
_delete(allUserDataList[index].id);
},
icon: const Icon(Icons.delete_forever)),
);
}),
),
),
);
}
}

model class of user data.

import 'local_database.dart';

/// model class of the user data

class UserData {
int? id;
String? firstName;
String? lastName;
String? address;
int? mobile;
int? age;

UserData(this.id, this.firstName, this.lastName, this.address, this.mobile,
this.age);

Map<String, dynamic> toMap() {
return {
LocalDatabase.columnId: id,
LocalDatabase.columnFirstName: firstName,
LocalDatabase.columnLastName: lastName,
LocalDatabase.columnAddress: address,
LocalDatabase.columnAge: age,
LocalDatabase.columnMobile: mobile,
};
}

UserData.fromMap(Map<String, dynamic> map) {
id = map['id'];
firstName = map['first_name'];
lastName = map['last_name'];
address = map['address'];
age = map['age'];
mobile = map['mobile'];
}
}

database class where actual queries are written related to database like creating a database, insert data to row, update and delete the data.

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

class LocalDatabase {
/// defining database name
static const _databaseName = "user_data.db";

/// version of db
static const _databaseVersion = 1;

/// table name
static const table = 'users_table';

/// defining the names of columns
static const columnId = 'id';
static const columnFirstName = 'first_name';
static const columnLastName = 'last_name';
static const columnAddress = 'address';
static const columnMobile = 'mobile';
static const columnAge = 'age';

/// making singleton class
LocalDatabase._privateConstructor();
static final LocalDatabase instance = LocalDatabase._privateConstructor();

/// creating reference of database
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;

/// initializing database if it accessed first time
_database = await _initDatabase();
return _database!;
}

/// here it will create the database in does not exist
/// if it exist it will open the database
_initDatabase() async {
String path = join(await getDatabasesPath(), _databaseName);
return await openDatabase(path,
version: _databaseVersion, onCreate: _onCreate);
}

/// here we will write the command to create database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY AUTOINCREMENT,
$columnFirstName TEXT NOT NULL,
$columnLastName TEXT NOT NULL,
$columnAddress TEXT NOT NULL,
$columnMobile INTEGER NOT NULL,
$columnAge INTEGER NOT NULL
)
''');
}

/// now we will create a function to insert the user data
/// by this function it will insert the row in db

Future<int> insertUserData(UserData userData) async {
Database db = await instance.database;
return await db.insert(table, {
'first_name': userData.firstName,
'last_name': userData.lastName,
'mobile': userData.mobile,
'address': userData.address,
'age': userData.age
});
}

/// here it will return all the rows as a list , list with key-value of column as a map
Future<List<Map<String, dynamic>>> getAllUserData() async {
Database db = await instance.database;
return await db.query(table);
}

/// it is used to query the row to get the data based on arguments passed
Future<List<Map<String, dynamic>>> getUserDataByName(name) async {
Database db = await instance.database;
return await db.query(table, where: "$columnFirstName LIKE '%$name%'");
}

/// here it will update values of the row
Future<int> updateUserData(UserData userData) async {
Database db = await instance.database;
int id = userData.toMap()['id'];
return await db.update(table, userData.toMap(),
where: '$columnId = ?', whereArgs: [id]);
}

/// to delete the entire row using an id which returns the number of rows affected
Future<int> deleteUserData(int id) async {
Database db = await instance.database;
return await db.delete(table, where: '$columnId = ?', whereArgs: [id]);
}
}

page to add or edit the user details.

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:sqflite_demo/user_data.dart';
import 'local_database.dart';

class AddEditUserData extends StatefulWidget {
/// constructor to get boolean response
final bool? isEdit;

/// constructor to get user data object
final UserData? userData;
const AddEditUserData({super.key, this.isEdit = false, this.userData});

@override
State<AddEditUserData> createState() => _AddEditUserDataState();
}

class _AddEditUserDataState extends State<AddEditUserData> {
/// creating controllers for all text editing controller
TextEditingController firstNameController = TextEditingController();
TextEditingController lastNameController = TextEditingController();
TextEditingController addressController = TextEditingController();
TextEditingController ageController = TextEditingController();
TextEditingController mobileController = TextEditingController();

/// creating instance of the local database
final localDatabase = LocalDatabase.instance;

/// insert function to add user data
_insert(firstName, lastName, address, age, mobile) async {
Map<String, dynamic> row = {
LocalDatabase.columnFirstName: firstName,
LocalDatabase.columnLastName: lastName,
LocalDatabase.columnAddress: address,
LocalDatabase.columnAge: age,
LocalDatabase.columnMobile: mobile
};
UserData userData = UserData.fromMap(row);
await localDatabase.insertUserData(userData);
Navigator.pop(context, true);
setState(() {});
}

/// to update the existing user data
_update(id, firstName, lastName, address, mobile, age) async {
UserData userData = UserData(id, firstName, lastName, address, mobile, age);
await localDatabase.updateUserData(userData);
Navigator.pop(context, true);
}

@override
void initState() {
super.initState();

/// checking isEdit boolean value
widget.isEdit == true ? setPrefilledData() : null;
}

/// to prefill the text editing controller value id isEdit id true
setPrefilledData() {
firstNameController.text = widget.userData!.firstName!;
lastNameController.text = widget.userData!.lastName!;
mobileController.text = widget.userData!.mobile.toString();
addressController.text = widget.userData!.address!;
ageController.text = widget.userData!.age.toString();
setState(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Text(widget.isEdit == true ? "Edit user data" : "Add user data "),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width * 0.9,
child: TextField(
controller: firstNameController,
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
label: const Text("First name"),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green))),
),
),
const SizedBox(
height: 20.0,
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width * 0.9,
child: TextField(
controller: lastNameController,
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
label: const Text("Last name"),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green))),
),
),
const SizedBox(
height: 20.0,
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width * 0.9,
child: TextField(
controller: addressController,
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
label: const Text("Address"),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green))),
),
),
const SizedBox(
height: 10.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width * 0.65,
child: TextField(
controller: mobileController,
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.top,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
label: const Text("Mobile number"),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green))),
),
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.05,
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width * 0.15,
child: TextField(
controller: ageController,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
label: const Text("Age"),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: const BorderSide(color: Colors.green))),
),
),
],
),
ElevatedButton(
onPressed: () {
String firstName = firstNameController.text;
String lastName = lastNameController.text;
String address = addressController.text;
int age = int.parse(ageController.text);
int mobile = int.parse(mobileController.text);

/// calling function to update and insert the data
widget.isEdit == true
? _update(widget.userData!.id, firstName, lastName,
address, mobile, age)
: _insert(firstName, lastName, address, age, mobile);
},
child: Text(widget.isEdit == true
? "Edit user data"
: "Save user data")),
],
),
),
);
}
}

There are also other packages which are used to store data locally like Hive, Flutter Secure Storage, etc which can be explored from the documentation itself.

Also explore the Firebase package, which is the most important package which provides a feature to store data in a cloud, and also other useful firebase features.

For the above sqflite example, here is the full code available.

In this article, we have explored shared preferences and sqfilte in detail with examples and with all the database operations.

Let me know in the comments if I need to correct anything. I will try to improve it.

Clap if you like the article. 👏

--

--