Enable Dark Mode!
what-are-the-types-of-data-storage-in-flutter.jpg
By: Jegdev S

What are the Types of Data Storage in Flutter

Technical Flutter

Flutter provides multiple data storage options to meet various application requirements. Data storage is a fundamental aspect of mobile app development, allowing applications to persist user data, cache information, and maintain state across app sessions.

In this comprehensive blog post, we'll explore the different types of data storage available in Flutter and demonstrate how to implement each one with practical code examples.

SharedPreferences (Key-Value Storage)

In Flutter, SharedPreferences provides a lightweight way to store small amounts of data, making it useful for things like user settings, preferences, or basic configuration options.

To use SharedPreferences in Flutter, the first step is to include its package as a dependency inside the pubspec.yaml file.

Step 1: Create a New Flutter Project

Start by setting up a new Flutter project:

flutter create shared_prefs_app
cd shared_prefs_app

This creates a basic app structure. Check our Flutter basics guide for more details.

Step 2: Add the Dependency

In your pubspec.yaml, add the shared_preferences package:

dependencies:
shared_preferences: ^2.2.2

Install the package by running:

flutter pub get

Then import the package in your Dart file:

Import 'package:shared_preferences/shared_preferences.dart' ;

Here's how to implement basic SharedPreferences operations:

import 'package:shared_preferences/shared_preferences.dart';
class SimplePrefs {
  static SharedPreferences? _prefs;
  
  static Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }
  
  // Save data
  static Future<void> saveString(String key, String value) async {
    await _prefs?.setString(key, value);
  }
  
  static Future<void> saveInt(String key, int value) async {
    await _prefs?.setInt(key, value);
  }
  
  static Future<void> saveBool(String key, bool value) async {
    await _prefs?.setBool(key, value);
  }
  
  // Get data
  static String getString(String key) => _prefs?.getString(key) ?? '';
  static int getInt(String key) => _prefs?.getInt(key) ?? 0;
  static bool getBool(String key) => _prefs?.getBool(key) ?? false;
  
  // Remove data
  static Future<void> remove(String key) async {
    await _prefs?.remove(key);
  }
}

Key Points:

  1. class SimplePrefs { static SharedPreferences? _prefs;}
    • Defines a class SimplePrefs to manage shared preferences.
    • _prefs is a static variable of type SharedPreferences?.
    • It is marked as nullable (?) because it will only be assigned after initialization.
  2. static Future <void> init() async { _prefs = await SharedPreferences.getInstance(); }
    • Make sure to call this method once before accessing any other features of the class.
    • SharedPreferences.getInstance() creates (or retrieves) a singleton instance of shared preferences.
    • The instance is stored in _prefs for later use.
  3. static Future <void> saveString(String key, String value) async {await _prefs?.setString(key, value); }
    • Saves a string value under the given key.
    • Example: SimplePrefs.saveString('username', 'John');
  4. static Future <void> saveInt(String key, int value) async {await _prefs?.setInt(key, value); }
    • Saves an integer value under the given key.
    • Example: SimplePrefs.saveInt('age', 25);
  5. static Future <void> saveBool(String key, bool value) async {await _prefs?.setBool(key, value);}
    • Saves a boolean value under the given key.
    • Example: SimplePrefs.saveBool('isLoggedIn', true);
  6. static String getString(String key) => _prefs?.getString(key) ?? '';
    • Fetches the string associated with the specified key.
    • Returns '' (empty string) if the key is not found.
    • Example: SimplePrefs.getString('username');
  7. static int getInt(String key) => _prefs?.getInt(key) ?? 0;
    • Retrieves the integer value stored with the given key.
    • Returns 0 if the key is not found.
    • Example: SimplePrefs.getInt('age');
  8. static bool getBool(String key) => _prefs?.getBool(key) ?? false;
    • Deletes the value associated with the given key.
    • Example: SimplePrefs.remove('username');

Example usage in your widget:

dart
// Initialize in main()
await SimplePrefs.init();
// Save data
await SimplePrefs.saveString('username', 'John');
await SimplePrefs.saveInt('age', 25);
await SimplePrefs.saveBool('isLoggedIn', true);
// Read data
String username = SimplePrefs.getString('username');
int age = SimplePrefs.getInt('age');
bool isLoggedIn = SimplePrefs.getBool('isLoggedIn');

SQLite Database (Local Database)

SQLite is a lightweight, serverless database perfect for storing structured data locally. It’s well-suited for handling structured data with complex relationships and allows the use of SQL queries

First, add the sqflite dependency to your pubspec.yaml:

dependencies:

  sqflite: ^2.3.0
  path: ^1.8.3

Import the required packages:

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

Here's a complete implementation of SQLite database operations:

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class SimpleDB {
  static Database? _database;
  static const String tableName = 'users';
  
  static Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB();
    return _database!;
  }
  
  static Future<Database> _initDB() async {
    String path = join(await getDatabasesPath(), 'app.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: (db, version) {
        return db.execute('''
          CREATE TABLE $tableName(
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT NOT NULL,
            age INTEGER NOT NULL
          )
        ''');
      },
    );
  }
  
  // Insert user
  static Future<int> insertUser(Map<String, dynamic> user) async {
    final db = await database;
    return await db.insert(tableName, user);
  }
  
  // Get all users
  static Future<List<Map<String, dynamic>>> getAllUsers() async {
    final db = await database;
    return await db.query(tableName);
  }
  
  // Update user
  static Future<int> updateUser(int id, Map<String, dynamic> user) async {
    final db = await database;
    return await db.update(tableName, user, where: 'id = ?', whereArgs: [id]);
  }
  
  // Delete user
  static Future<int> deleteUser(int id) async {
    final db = await database;
    return await db.delete(tableName, where: 'id = ?', whereArgs: [id]);
  }
}

Key Points:

  1. class SimpleDB {static Database? _database; static const String tableName = 'users';
    • Defines a class SimpleDB to handle all database operations.
    • _database is a static variable that holds the database instance.
    • tableName is a constant string, here set as 'users'. This represents the name assigned to the database table.
  2. static Future<Database> get database async {if (_database != null) return _database!; _database = await _initDB();return _database!;  }
    • Provides a getter for the database.
    • If _database is already initialized, it simply returns it.
    • Otherwise, it calls _initDB() to open (or create) the database.
    • Ensures the database is a singleton (only one instance is used).
  3. static Future<int> insertUser(Map<String, dynamic> user) async {final db = await database;return await db.insert(tableName, user);  }
    • Inserts a new user record into the database.
    • Example:user is passed as a Map<String, dynamic> (key-value pairs).
    • Returns the id of the newly inserted row.
  4. static Future<List<Map<String, dynamic>>> getAllUsers() async {final db = await database; return await db.query(tableName); }
    • Fetches all users from the users table.
    • Returns a list of maps, where each map represents a row.
  5. static Future<int> updateUser(int id, Map<String, dynamic> user) async {final db = await database;return await db.update(tableName, user, where: 'id = ?', whereArgs: [id]);  }
    • Updates an existing user’s data based on their id.
    • where: 'id = ?' ensures only the row with that specific ID is updated.
    • Returns the number of rows affected (usually 1).
  6. static Future<int> deleteUser(int id) async {final db = await database;return await db.delete(tableName, where: 'id = ?', whereArgs: [id]); }
    • Deletes a user from the table based on their id.
    • Returns the number of rows removed (usually 1).

Example usage:

await SimpleDB.insertUser({
  'name': 'John Doe',
  'email': 'john@example.com',
  'age': 25,
});
// Get all users
List<Map<String, dynamic>> users = await SimpleDB.getAllUsers();
// Update user
await SimpleDB.updateUser(1, {
  'name': 'Jane Doe',
  'email': 'jane@example.com',
  'age': 30,
});
// Delete user
await SimpleDB.deleteUser(1);

Hive Database (NoSQL Local Database)

Hive is a lightweight and efficient NoSQL database built for Flutter apps. It allows you to store various data types like strings, numbers, booleans, lists, and even custom objects directly on the device. This makes it ideal for scenarios such as caching user preferences, saving app state, or storing offline data. For example, you can use Hive to persist login details so users stay signed in across sessions. Unlike traditional databases, Hive doesn't require a server and works entirely locally, supporting large datasets without performance issues.

Step 1: Set Up a New Flutter Project

Start by creating a fresh Flutter project via the command line:

flutter create my_hive_app

Navigate to the project folder:

cd my_hive_app

Step 2: Install Dependencies

Update your pubspec.yaml file with the necessary packages under dependencies and dev_dependencies:

dependencies:
  flutter:
    sdk: flutter
  hive: ^2.2.3
  hive_flutter: ^1.1.0
dev_dependencies:
  hive_generator: ^2.0.1
  build_runner: ^2.4.7

Run this command to fetch the packages:

flutter pub get

Step 3: Project Folder Overview

Your project should look like this (key files highlighted):

  • lib/
    • main.dart
    • user_model.dart
    • hive_helper.dart
    • hive_example.dart (or your screen file)
  • pubspec.yaml

We'll add and modify these files as we go.

Step 4: Define Your Data Model

Create a file lib/user_model.dart for your custom object. Use Hive annotations to make it storable:

import 'package:hive/hive.dart';
part 'user_model.g.dart';
@HiveType(typeId: 0)
class UserModel extends HiveObject {
  @HiveField(0)
  String name;
  @HiveField(1)
  String email;
  @HiveField(2)
  int age;
  @HiveField(3)
  bool isActive;
  UserModel({
    required this.name,
    required this.email,
    required this.age,
    this.isActive = true,
  });
  @override
  String toString() {
    return 'UserModel{name: $name, email: $email, age: $age, isActive: $isActive}';
  }
}

Generate the adapter with:

flutter pub run build_runner build

This creates user_model.g.dart for serialization.

Step 5: Set Up Hive Initialization

In lib/main.dart, initialize Hive before running the app:

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'user_model.dart'; // Import your model
import 'hive_helper.dart'; // We'll create this next
import 'hive_example.dart'; // Your example screen
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  Hive.registerAdapter(UserModelAdapter());
  await HiveHelper.init();
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hive Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HiveExample(),
    );
  }
}

This class handles CRUD (Create, Read, Update, Delete) actions.

Step 6: Create a Hive Helper Class

In lib/hive_helper.dart, build a utility class for database operations:

class HiveHelper {
  static const String userBoxName = 'users';
  // Initialize Hive
  static Future<void> init() async {
    await Hive.initFlutter();
    Hive.registerAdapter(UserModelAdapter());
    await Hive.openBox<UserModel>(userBoxName);
  }
  // Get user box
  static Box<UserModel> getUserBox() {
    return Hive.box<UserModel>(userBoxName);
  }
  // Add user
  static Future<void> addUser(UserModel user) async {
    final box = getUserBox();
    await box.add(user);
  }
  // Get all users
  static List<UserModel> getAllUsers() {
    final box = getUserBox();
    return box.values.toList();
  }
  // Get user by index
  static UserModel? getUser(int index) {
    final box = getUserBox();
    return box.getAt(index);
  }
  // Update user
  static Future<void> updateUser(int index, UserModel user) async {
    final box = getUserBox();
    await box.putAt(index, user);
  }
  // Delete user
  static Future<void> deleteUser(int index) async {
    final box = getUserBox();
    await box.deleteAt(index);
  }
  // Delete all users
  static Future<void> deleteAllUsers() async {
    final box = getUserBox();
    await box.clear();
  }
  // Close box
  static Future<void> close() async {
    final box = getUserBox();
    await box.close();
  }
}

Key Features:

  1. class HiveHelper { static const String userBoxName = 'users';
    • Defines a helper class HiveHelper to manage Hive database operations.
    • userBoxName is a constant string used to identify the Hive box (like a table in SQL, or a collection in NoSQL).
    • Here, the box is named "users".
  2. static Future
    • Hive.initFlutter() > Initializes Hive for Flutter.
    • Hive.registerAdapter(UserModelAdapter()) > Registers an adapter for the UserModel class (needed because Hive can’t store custom objects without telling it how to serialize/deserialize them).
    • Hive.openBox<UserModel>(userBoxName) > Opens the "users" box for storing UserModel objects. If it doesn’t exist, it creates one.
  3. static Box<UserModel> getUserBox() {return Hive.box<UserModel>(userBoxName);}
    • Returns the already opened users box.
    • This method is used internally by other helper functions.
  4. static Future<void> addUser(UserModel user) async {final box = getUserBox(); await box.add(user);}
    • Inserts a new user into the users box.
    • Hive assigns an auto-incremented index (like an ID).
  5. static List<UserModel> getAllUsers() {final box = getUserBox();return box.values.toList();}
    • Retrieves all stored users as a list of UserModel objects.
  6. static UserModel? getUser(int index) {final box = getUserBox();return box.getAt(index); }
    • Fetches a user by its index (position in the box).
    • Returns null if the index doesn’t exist.
  7. static Future<void> updateUser(int index, UserModel user) async {final box = getUserBox(); await box.putAt(index, user);}
    • Updates a user at the given index.
    • Replaces the old object with the new UserModel.
  8. static Future<void> deleteUser(int index) async {final box = getUserBox();await box.deleteAt(index);}
    • Deletes a user at the specified index.
  9. static Future<void> deleteAllUsers() async {final box = getUserBox();await box.clear(); }
    • Removes all entries from the users box.
  10. static Future<void> close() async {final box = getUserBox(); await box.close(); }
    • Closes the users box when it’s no longer needed.
    • Helps free resources and avoid memory leaks.
    • Usually called when the app is shutting down.

Example implementation:

class _HiveExampleState extends State<HiveExample> {
  List<UserModel> _users = [];
  @override
  void initState() {
    super.initState();
    _loadUsers();
  }
  void _loadUsers() {
    setState(() {
      _users = HiveHelper.getAllUsers();
    });
  }
  Future<void> _addUser() async {
    final newUser = UserModel(
      name: 'Alice Smith',
      email: 'alice@example.com',
      age: 28,
      isActive: true,
    );
    
    await HiveHelper.addUser(newUser);
    _loadUsers();
    print('User added successfully!');
  }
  Future<void> _updateUser(int index) async {
    final updatedUser = UserModel(
      name: 'Bob Johnson',
      email: 'bob@example.com',
      age: 32,
      isActive: false,
    );
    
    await HiveHelper.updateUser(index, updatedUser);
    _loadUsers();
    print('User updated successfully!');
  }
  Future<void> _deleteUser(int index) async {
    await HiveHelper.deleteUser(index);
    _loadUsers();
    print('User deleted successfully!');
  }
}

File Storage (Documents and Cache)

File storage allows you to save and retrieve files from the device's file system. This is useful for storing documents, images, cache data, and large text files.

Add the path_provider dependency:

dependencies:
  path_provider: ^2.1.1

Import the required packages:

import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:convert';

Here's a comprehensive file storage helper:

import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:convert';
class SimpleFile {
  // Write text to file
  static Future<void> writeText(String fileName, String content) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$fileName');
    await file.writeAsString(content);
  }
  
  // Read text from file
  static Future<String> readText(String fileName) async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      final file = File('${directory.path}/$fileName');
      return await file.readAsString();
    } catch (e) {
      return '';
    }
  }
  
  // Write JSON to file
  static Future<void> writeJson(String fileName, Map<String, dynamic> data) async {
    final jsonString = jsonEncode(data);
    await writeText(fileName, jsonString);
  }
  
  // Read JSON from file
  static Future<Map<String, dynamic>?> readJson(String fileName) async {
    try {
      final jsonString = await readText(fileName);
      if (jsonString.isEmpty) return null;
      return jsonDecode(jsonString);
    } catch (e) {
      return null;
    }
  }
  
  // Check if file exists
  static Future<bool> exists(String fileName) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$fileName');
    return await file.exists();
  }
  
  // Delete file
  static Future<void> delete(String fileName) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$fileName');
    if (await file.exists()) {
      await file.delete();
    }
  }
}

Key Methods:

  1. class SimpleFile {
    • Defines a utility class SimpleFile that makes it easier to perform file read, write, JSON handling, and deletion.
  2. static Future<void> writeText(String fileName, String content) async{final directory = await getApplicationDocumentsDirectory();final file = File('${directory.path}/$fileName');await file.writeAsString(content); }
    • Gets the app’s documents directory (a safe, persistent location for files).
    • Creates a file with the given fileName.
    • Writes the content string into the file.
  3. static Future<String> readText(String fileName) async {try {final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/$fileName');return await file.readAsString(); }
    • Reads the contents of a file as text
    • Returns the file content if it exists.
    • If the file doesn’t exist or an error occurs, it returns an empty string.
  4. static Future<void> writeJson(String fileName, Map<String, dynamic> data) async {final jsonString = jsonEncode(data); await writeText(fileName, jsonString);}
    • Converts a Dart map into a JSON string using jsonEncode.
    • Saves the JSON string into the file using the writeText method.
  5. static Future<Map<String, dynamic>?> readJson(String fileName) async {try {final jsonString = await readText(fileName);if (jsonString.isEmpty) return null;return jsonDecode(jsonString);} catch (e) {return null; } }
    • Reads the file content as text.
    • If empty > returns null.
    • Otherwise, decodes the JSON string into a Map.
  6. static Future<bool> exists(String fileName) async {final directory = await getApplicationDocumentsDirectory();final file = File('${directory.path}/$fileName');return await file.exists(); }
    • Checks whether the file exists in the documents directory.
    • Returns true if the file exists, otherwise false.
  7. static Future<void> delete(String fileName) async {final directory = await getApplicationDocumentsDirectory();final file = File('${directory.path}/$fileName');if (await file.exists()) { await file.delete();}}
    • Deletes the file if it exists.
    • Safe to call even if the file doesn’t exist (it checks first).

Example usage:

// Write text file
await SimpleFile.writeText('notes.txt', 'Hello World!');
// Read text file
String content = await SimpleFile.readText('notes.txt');
// Write JSON file
await SimpleFile.writeJson('user.json', {
  'name': 'John',
  'email': 'john@example.com'
});
// Read JSON file
Map<String, dynamic>? userData = await SimpleFile.readJson('user.json');
// Check if file exists
bool exists = await SimpleFile.exists('notes.txt');
// Delete file
await SimpleFile.delete('notes.txt');

When to Use Each Option


Storage TypeBest ForProsCons
SharedPreferencesSettings, preferences, small dataSimple, fastLimited to basic data types
SQLiteComplex data, relationshipsPowerful queries, reliableMore complex setup
HiveObjects, structured dataFast, modern, easy to useNewer, less documentation
File StorageLarge files, documents, imagesFlexible, handles any data typeManual file management

Summary

  • SharedPreferences are ideal for storing simple key-value pairs like user settings, preferences, and small configuration data. They're easy to implement and perfect for lightweight storage needs.
  • SQLite Database offers a powerful solution for complex, structured data with relationships. It supports SQL queries and is perfect for apps requiring robust data management and complex querying capabilities.
  • Hive Database provides a modern, fast NoSQL approach that's specifically optimized for Flutter applications. It offers excellent performance and is ideal for storing objects and structured data without the complexity of SQL.
  • File Storage enables you to save and manage documents, images, cache data, and large text files directly in the device's file system. It's perfect for apps that need to handle multimedia content or large datasets.

Data storage is a crucial component of mobile app development, and Flutter provides multiple robust solutions to meet diverse requirements:

By leveraging these powerful storage options, Flutter developers can create applications that efficiently manage data persistence, ensuring smooth user experiences and reliable data management across different app scenarios.

Whether you're building a simple settings app, a complex data-driven application, or a multimedia-rich platform, choosing the right storage solution can significantly impact your app's performance and user satisfaction.

To read more about How to Use Different Types of Buttons in Flutter, refer to our blog How to Use Different Types of Buttons in Flutter.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message