Local Data Storage in Flutter
Local Data Storage means saving application data directly on the user's device so that the data can be accessed later without requesting it again from a remote server. Flutter provides several approaches for local persistence depending on the type and amount of data that needs to be stored.
Local storage is useful for saving user preferences, login states, application settings, cached information, offline data, files, and structured records. Flutter's persistence options include key-value storage, local files, and SQLite databases.
1. What is Local Data Storage?
Local Data Storage is the process of storing information on the device where the Flutter application is installed.
For example, an application may need to remember:
- Whether the user has completed onboarding.
- The selected language.
- Dark mode or light mode preference.
- A login or session-related flag.
- Shopping cart information.
- Recently viewed data.
- Offline application data.
- Documents, images, text files, or other local files.
Simple Flow
User Action
↓
Flutter Application
↓
Local Storage Layer
↓
Device Storage
↓
Data remains available for later use
2. Why Use Local Data Storage?
- It allows data to remain available after the application is closed.
- It can reduce unnecessary network requests.
- It improves application responsiveness.
- It supports offline functionality.
- It can store user preferences and application configuration.
- It can cache information received from an API.
- It provides a convenient way to persist application state.
3. Types of Local Storage in Flutter
| Storage Method | Best Used For | Example |
|---|
| SharedPreferences | Small key-value data | Theme, language, onboarding status |
| File Storage | Files and text data | JSON, text files, downloaded documents |
| SQLite | Structured and relational data | Products, users, orders, tasks |
| Secure Storage | Sensitive values | Tokens and credentials |
| Cache | Temporary/reusable data | Images and API responses |
The correct storage method depends on the type, size, structure, lifetime, and security requirements of the data.
4. SharedPreferences
shared_preferences is commonly used when an application needs to store a relatively small collection of key-value pairs. Flutter's documentation describes it as a solution for simple persistent key-value data.
Common Use Cases
- Dark mode preference
- Language preference
- Onboarding completion
- Simple application settings
- Small counters
- Simple boolean flags
- Non-sensitive user preferences
Install SharedPreferences
flutter pub add shared_preferences
Import Package
import 'package:shared_preferences/shared_preferences.dart';
Supported Data Types
| Type | Example | Method |
|---|
| String | Manish | setString() |
| int | 25 | setInt() |
| double | 99.50 | setDouble() |
| bool | true | setBool() |
| List | ["Flutter","Dart"] | setStringList() |
Save String Data
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'Manish');
Read String Data
final prefs = await SharedPreferences.getInstance();
String username = prefs.getString('username') ?? 'Guest';
print(username);
Save Integer
await prefs.setInt('age', 25);
Read Integer
int age = prefs.getInt('age') ?? 0;
Save Boolean
await prefs.setBool('isLoggedIn', true);
Read Boolean
bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
Save Double
await prefs.setDouble('price', 499.99);
Read Double
double price = prefs.getDouble('price') ?? 0.0;
Save List
await prefs.setStringList(
'skills',
['Flutter', 'Dart', 'Firebase'],
);
Read List
List skills =
prefs.getStringList('skills') ?? [];
5. Complete SharedPreferences Example
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const StoragePage(),
);
}
}
class StoragePage extends StatefulWidget {
const StoragePage({super.key});
@override
State createState() => _StoragePageState();
}
class _StoragePageState extends State {
String username = '';
Future saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'Manish');
setState(() {
username = 'Manish';
});
}
Future loadData() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
username = prefs.getString('username') ?? 'No Data';
});
}
Future removeData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
setState(() {
username = 'Data Removed';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Local Storage'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
'Username: $username',
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: saveData,
child: const Text('Save Data'),
),
ElevatedButton(
onPressed: loadData,
child: const Text('Load Data'),
),
ElevatedButton(
onPressed: removeData,
child: const Text('Remove Data'),
),
],
),
),
);
}
}
6. Removing Local Data
The remove() method removes a particular key and its associated value.
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
Check Whether a Key Exists
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('username')) {
print('Username exists');
}
Clear Stored Preferences
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
Use clear() carefully because it removes all stored preference values managed by that preference store.
7. Login Status Example
A simple login state can be stored as a boolean value.
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isLoggedIn', true);
Later, the application can read the value:
final prefs = await SharedPreferences.getInstance();
bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
if (isLoggedIn) {
print('User is logged in');
} else {
print('User is logged out');
}
Important
A simple preference flag is not the same as securely storing authentication credentials. Passwords, private keys, and other sensitive secrets should not be stored as ordinary SharedPreferences values.
8. Onboarding Example
Many applications display onboarding screens only the first time the application is opened.
final prefs = await SharedPreferences.getInstance();
bool completed =
prefs.getBool('onboardingCompleted') ?? false;
After the user finishes onboarding:
await prefs.setBool('onboardingCompleted', true);
On the next application launch, the application can check this value and decide whether to display onboarding or the main screen.
9. Theme Preference
Local storage can remember whether the user selected dark mode.
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('darkMode', true);
Read the value:
final prefs = await SharedPreferences.getInstance();
bool darkMode = prefs.getBool('darkMode') ?? false;
This type of key-value storage is appropriate for simple application configuration such as theme settings.
10. Storing JSON Data Locally
SharedPreferences is designed for simple supported types. If an application needs to store a small object, the object can be converted to JSON and then stored as a String.
Convert Object to JSON
import 'dart:convert';
Map user = {
'name': 'Manish',
'age': 25,
'city': 'Mumbai',
};
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'user',
jsonEncode(user),
);
Read JSON Data
final prefs = await SharedPreferences.getInstance();
String? data = prefs.getString('user');
if (data != null) {
Map user = jsonDecode(data);
print(user['name']);
print(user['age']);
}
Important
JSON stored in a key-value store can be useful for small objects, but it is not a replacement for a proper database when the application contains large or complex datasets.
11. File Storage
When an application needs to store actual files or larger text content, file storage can be used. Flutter's path_provider package provides commonly used filesystem locations, while Dart's dart:io provides the File API for reading and writing files on supported mobile and desktop platforms.
Install path_provider
flutter pub add path_provider
Import Packages
import 'dart:io';
import 'package:path_provider/path_provider.dart';
Get Application Documents Directory
Future getApplicationDirectory() async {
return await getApplicationDocumentsDirectory();
}
Write a File
Future saveFile() async {
final directory =
await getApplicationDocumentsDirectory();
final file = File('${directory.path}/data.txt');
await file.writeAsString('Hello Flutter');
}
Read a File
Future readFile() async {
final directory =
await getApplicationDocumentsDirectory();
final file = File('${directory.path}/data.txt');
if (await file.exists()) {
return await file.readAsString();
}
return '';
}
File Storage Use Cases
- Saving text documents
- Saving generated files
- Storing downloaded content
- Saving JSON files
- Offline documents
- Application-generated reports
12. SQLite Database
When the application needs structured data, relationships, filtering, sorting, and database queries, SQLite is a suitable local persistence approach.
Flutter documentation demonstrates SQLite persistence using the sqflite package. It is particularly useful for applications that need to store and query larger amounts of structured data.
Install Packages
flutter pub add sqflite path
Import Packages
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
Open a Database
final database = await openDatabase(
join(
await getDatabasesPath(),
'app_database.db',
),
version: 1,
);
Create a Table
final database = await openDatabase(
join(
await getDatabasesPath(),
'app_database.db',
),
version: 1,
onCreate: (db, version) async {
await db.execute(
'CREATE TABLE users('
'id INTEGER PRIMARY KEY, '
'name TEXT, '
'email TEXT'
')',
);
},
);
Insert Data
await database.insert(
'users',
{
'name': 'Manish',
'email': '[email protected]',
},
);
Read Data
final users = await database.query('users');
for (final user in users) {
print(user);
}
Update Data
await database.update(
'users',
{
'name': 'Manish Negi',
},
where: 'id = ?',
whereArgs: [1],
);
Delete Data
await database.delete(
'users',
where: 'id = ?',
whereArgs: [1],
);
13. SharedPreferences vs File Storage vs SQLite
| Feature | SharedPreferences | File Storage | SQLite |
|---|
| Data Type | Simple key-value | Files/text | Structured records |
| Complex Queries | No | No | Yes |
| Relationships | No | No | Yes |
| Best For | Settings and preferences | Documents and files | Large structured data |
| Example | Dark mode | PDF/document | Product database |
| SQL Support | No | No | Yes |
14. Choosing the Correct Storage Method
What type of data do you need to store?
|
+-- Simple key-value?
| |
| +-- Use SharedPreferences
|
+-- Files or text?
| |
| +-- Use File Storage
|
+-- Structured records?
|
+-- Use SQLite / Database
Example Decision
| Requirement | Recommended Approach |
|---|
| Dark mode setting | SharedPreferences |
| Selected language | SharedPreferences |
| Onboarding completed flag | SharedPreferences |
| Downloaded text file | File Storage |
| Generated PDF | File Storage |
| Thousands of products | SQLite |
| Orders and customers | SQLite |
| Relational data | SQLite |
15. Local Storage and Offline Applications
Local storage plays an important role in offline-first applications. An application can save information locally and display it even when the network is temporarily unavailable.
Typical Offline Flow
Internet Available
↓
Fetch Data from API
↓
Save Data Locally
↓
Display Data
↓
Internet Unavailable
↓
Read Cached/Stored Data
↓
Display Previously Saved Data
This approach can improve the user experience because the application does not always have to wait for a network response before displaying previously available information.
16. Data Storage Service
Instead of directly calling SharedPreferences from every widget, it is often cleaner to create a separate service class responsible for local storage.
Example Storage Service
import 'package:shared_preferences/shared_preferences.dart';
class StorageService {
Future saveUsername(String username) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
}
Future getUsername() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('username') ?? '';
}
Future removeUsername() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
}
}
Why Create a Service?
- It keeps storage logic separate from UI code.
- It improves code organization.
- It makes code easier to reuse.
- It simplifies testing.
- It makes it easier to replace the underlying storage implementation later.
17. Repository Pattern for Local Storage
For larger applications, a repository can act as the application's interface to stored data.
UI
↓
ViewModel / Controller
↓
Repository
↓
Local Storage Service
↓
SharedPreferences / SQLite / File Storage
This architecture prevents UI components from becoming tightly coupled to a particular storage package.
18. Important Security Considerations
Local storage should not automatically be considered secure storage.
Avoid storing highly sensitive information in ordinary key-value storage without appropriate protection.
- Do not store plain-text passwords.
- Do not store private encryption keys in ordinary preferences.
- Do not assume local data cannot be accessed on a compromised device.
- Use an appropriate secure-storage solution for sensitive credentials.
- Store only the data that the application actually needs.
19. Common Mistakes
Mistake 1: Using SharedPreferences for Large Data
SharedPreferences is intended for relatively small key-value data, not large databases.
Mistake 2: Storing Passwords Directly
Ordinary local preferences should not be treated as a secure password vault.
Mistake 3: Forgetting Async Operations
Many local-storage operations are asynchronous and should be handled with async and await.
Future saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('name', 'Manish');
}
Mistake 4: Using Incorrect Getter Types
The type used for reading should correspond to the type used when storing the value.
await prefs.setInt('age', 25);
int age = prefs.getInt('age') ?? 0;
Mistake 5: Mixing Storage Logic with UI
For larger applications, separate storage operations into services or repositories instead of putting all persistence code directly inside widgets.
20. Best Practices
- Choose the storage mechanism according to the data requirements.
- Use SharedPreferences for simple preferences and small key-value values.
- Use files for file-oriented data.
- Use SQLite for structured and queryable data.
- Keep storage operations outside complex UI widgets when possible.
- Use meaningful and consistent storage keys.
- Handle missing values with sensible defaults.
- Handle asynchronous operations correctly.
- Do not store passwords as ordinary preferences.
- Do not use a key-value store as a replacement for a relational database.
- Test local storage operations.
- Consider offline requirements while designing the data layer.
21. Practical Project Example
Consider a Flutter shopping application.
| Data | Storage |
|---|
| Dark mode | SharedPreferences |
| Selected language | SharedPreferences |
| Onboarding status | SharedPreferences |
| Recently viewed products | SharedPreferences or local database depending on size |
| Product catalog | SQLite/local database for substantial structured data |
| Downloaded invoices | File Storage |
| Authentication secrets | Secure storage solution |
Application Flow
Flutter Shopping App
|
+-- User Preferences
| ↓
| SharedPreferences
|
+-- Product Data
| ↓
| Local Database
|
+-- Downloaded Invoice
| ↓
| File Storage
|
+-- Sensitive Credentials
↓
Secure Storage
22. Local Data Storage Interview Questions
Q1. What is local data storage?
Local data storage is the process of saving application data on the user's device so it can be accessed later.
Q2. What is SharedPreferences used for?
It is used for relatively small key-value data such as preferences, settings, flags, and simple application configuration.
Q3. What data types can SharedPreferences store?
Common supported types are int, double, bool, String, and List.
Q4. When should SQLite be used?
SQLite is appropriate when the application needs structured, queryable, and potentially larger amounts of local data.
Q5. When should file storage be used?
File storage is useful when the application needs to save documents, text, JSON files, generated files, or other file-based content.
Q6. Can SharedPreferences replace SQLite?
No. SharedPreferences is intended for simple key-value storage, while SQLite is designed for structured data and database queries.
Q7. Why should passwords not be stored in ordinary SharedPreferences?
Ordinary preference storage should not be treated as a secure credential store. Sensitive credentials require an appropriate secure-storage mechanism.
Q8. What is offline storage?
Offline storage allows an application to keep information locally so that previously stored data can be accessed when the network is unavailable.
23. Summary
- Local Data Storage allows Flutter applications to persist information on the device.
- SharedPreferences is suitable for relatively small key-value data.
- File storage is useful for documents and file-based data.
- SQLite is useful for structured and queryable local data.
- Local storage can support offline application functionality.
- Storage operations should be handled asynchronously where required.
- Sensitive information should use an appropriate secure-storage mechanism.
- For larger applications, services and repositories can separate storage logic from the UI.
24. Learn More About Flutter
JustAcademy Flutter Training Course
Register for Flutter Course Demo