Step-by-Step Guide: How to Store Data Locally in Flutter
Building Flutter apps that remember user preferences or app states requires implementing local data persistence. Flutter supports multiple local storage techniques—each tailored to different scenarios. Let’s explore the three most common methods: Shared Preferences, SQLite, and File Storage.
1. Shared Preferences: Lightweight Key-Value Storage
The shared_preferences plugin is best for storing small pieces of primitive data such as int, double, bool, String, and List<String>. It persists across app restarts and is very easy to use.
Add this dependency in your pubspec.yaml:
dependencies:
shared_preferences: ^2.2.0
Import it:
import 'package:shared_preferences/shared_preferences.dart';
Save and read values:
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', true);
bool isDarkMode = prefs.getBool('isDarkMode') ?? false;
This approach is straightforward and ideal for small persistent data such as user settings.
2. SQLite: Structured Local Database
For complex relational data, Flutter apps can use the sqflite plugin together with path to set up a full SQLite database on the device.
Add the dependencies:
dependencies:
sqflite: ^2.3.0
path: ^1.9.0
Create and initialize a database:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> initDB() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'my_app.db');
return openDatabase(path, version: 1, onCreate: (db, version) {
return db.execute(
'CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT, content TEXT)',
);
});
}
Insert and query data:
Future<void> insertNote(Note note) async {
final db = await initDB();
await db.insert('notes', note.toMap());
}
Future<List<Note>> getNotes() async {
final db = await initDB();
final maps = await db.query('notes');
return List.generate(maps.length, (i) => Note.fromMap(maps[i]));
}
SQLite is powerful for structured data, like note-taking apps or local databases with multiple records.
3. File Storage: Custom Data Persistence
For cases where you need to store raw files such as logs, CSVs, or JSON dumps, you can use the path_provider package to find directories and Dart’s File class to read/write.
Add this dependency:
dependencies:
path_provider: ^2.1.1
Create a helper class:
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class FileStorage {
Future<String> get _dir async =>
(await getApplicationDocumentsDirectory()).path;
Future<File> _file(String name) async => File('${await _dir}/$name');
Future<void> writeData(String fileName, String content) async {
final f = await _file(fileName);
await f.writeAsString(content);
}
Future<String> readData(String fileName) async {
final f = await _file(fileName);
return await f.readAsString();
}
}
Usage:
final storage = FileStorage();
await storage.writeData('notes.txt', 'Remember to drink water.');
String text = await storage.readData('notes.txt');
This method gives you full control over file handling.
Comparison
| Storage Method | Data Type | Best For | Complexity |
|---|---|---|---|
| Shared Preferences | Key–value (primitives) | Settings, small flags, user choices | Low |
| SQLite | Relational structured | Notes, lists, offline databases | Medium |
| File Storage | Custom/raw files | Logs, JSON/CSV export, offline files | Medium |
Final Thoughts
Flutter provides flexible options for local storage:
- Use Shared Preferences for simple key-value settings.
- Use SQLite when you need a relational, queryable database.
- Use File Storage when handling custom file data.
Choosing the right method depends on your app’s requirements. Each solution comes with its own strengths, helping you create more robust and user-friendly Flutter apps.

