Hive, shared_preferences, and flutter_secure_storage cover three different needs in Flutter: fast key–value storage, simple user preferences, and secure secret storage. This article compares them, explains when to use which, and shows how to set each one up so you don’t misuse local storage by accident.
Audience: IntermediateTested on: Flutter 3.x, Dart 3.x, Android 14 / iOS 17, macOS 14
High-level comparison
| Package | Best for | Data model | Security | Platforms |
|---|---|---|---|---|
| shared_preferences | Small user settings (theme, flags) | Simple key–value (primitives only) | Not encrypted | Android, iOS, Web, Desktop |
| Hive | Local databases / offline caches | Boxes with typed objects | Optional encryption | Android, iOS, Web, Desktop |
| flutter_secure_storage | Secrets (tokens, keys, PINs) | Key–value for small secrets | Encrypted via Keychain / Keystore | Android, iOS, some Desktop/Web via plugins |
When to use which
- Use shared_preferences when you need a few simple, non-sensitive settings:
- Dark mode on/off
- “Don’t show this again” flags
- Last selected tab or filter
- Use Hive when you need structured local data or offline-first behavior:
- Caching API responses
- Local todo items / notes / domain models
- Lists and objects that outgrow simple preferences
- Use flutter_secure_storage when you store anything sensitive:
- Access/refresh tokens
- Encryption keys
- Local PINs / biometric flags
shared_preferences: quick setup and usage
shared_preferences is built for small amounts of primitive data. It’s part of the official Flutter plugins family and works on mobile, web, and desktop. See the docs at pub.dev.
# pubspec.yaml
dependencies:
shared_preferences: ^latest
// lib/services/app_prefs.dart
import 'package:shared_preferences/shared_preferences.dart';
class AppPrefs {
AppPrefs(this._prefs);
final SharedPreferences _prefs;
static Future<AppPrefs> load() async {
final prefs = await SharedPreferences.getInstance();
return AppPrefs(prefs);
}
bool get isDarkMode => _prefs.getBool('isDarkMode') ?? false;
Future<bool> setDarkMode(bool value) => _prefs.setBool('isDarkMode', value);
String? get lastEmail => _prefs.getString('lastEmail');
Future<bool> setLastEmail(String value) => _prefs.setString('lastEmail', value);
}
Keep keys centralized in one class to avoid typos scattered across the app.
Hive: a lightweight local database
Hive is a fast key–value database written in Dart, good for structured data and offline caches. It supports custom object types via adapters and can encrypt boxes. Documentation is on Hive’s pub.dev page.
# pubspec.yaml
dependencies:
hive: ^latest
path_provider: ^latest # for mobile/desktop storage path
dev_dependencies:
hive_generator: ^latest
build_runner: ^latest
Define a model and generate a type adapter:
// lib/models/todo.dart
import 'package:hive/hive.dart';
part 'todo.g.dart';
@HiveType(typeId: 1)
class Todo extends HiveObject {
@HiveField(0)
String title;
@HiveField(1)
bool done;
Todo({required this.title, this.done = false});
}
# Generate adapter
dart run build_runner build
Initialize Hive and open a box at app startup:
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final dir = await getApplicationDocumentsDirectory();
Hive
..init(dir.path)
..registerAdapter(TodoAdapter());
await Hive.openBox<Todo>('todos');
runApp(const MyApp());
}
Use the box like a small local database:
final box = Hive.box<Todo>('todos');
await box.add(Todo(title: 'Buy milk'));
final all = box.values.toList();
flutter_secure_storage: safe place for secrets
flutter_secure_storage stores key–value pairs in platform secure storage: Keychain on iOS, Keystore on Android, etc. It’s the right choice for tokens and secrets. See its pub.dev page for platform details.
# pubspec.yaml
dependencies:
flutter_secure_storage: ^latest
// lib/services/secure_store.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStore {
SecureStore(this._storage);
final FlutterSecureStorage _storage;
static const _tokenKey = 'authToken';
Future<void> saveToken(String token) async {
await _storage.write(key: _tokenKey, value: token);
}
Future<String?> readToken() => _storage.read(key: _tokenKey);
Future<void> clearToken() async {
await _storage.delete(key: _tokenKey);
}
}
Remember that secure storage is not designed for large blobs. Keep secrets small (tokens, keys, flags) and store non-sensitive data elsewhere.
Combining them in a real app
A typical production app uses all three layers together:
- flutter_secure_storage for auth tokens and encryption keys.
- Hive for cached API responses and domain data (such as user profiles, timelines, offline drafts).
- shared_preferences for user preferences and UI flags (theme, onboarding, last-selected tab).
One common pattern is to store a token in secure storage, then cache non-sensitive user data in Hive keyed by user ID. Preferences like “dark mode” and “show completed todos” live in shared_preferences.
Security & pitfalls
- Do not store secrets in shared_preferences or Hive unencrypted. Access tokens, passwords, and keys belong in secure storage only.
- Beware of backups and rooted devices. Even secure storage can be at risk on rooted/jailbroken devices. Always assume local storage is attackable and keep tokens scoped and revocable.
- Migration pain. Changing box schemas (Hive) or keys (shared_preferences) needs a migration plan. Wrap access behind a small service layer so you can adjust internals later.
- Storing too much in secure storage. Large payloads can slow things down and cause timeouts on older devices. Keep secrets minimal.
FAQ
Q: Can I just use Hive for everything and skip shared_preferences?
A: You could store preferences in Hive, but shared_preferences is simpler and built exactly for small key–value settings. Many teams still prefer it for clarity, while Hive handles heavier structured data.
Q: Should I encrypt Hive boxes if I’m already using flutter_secure_storage?
A: Encrypt Hive only when you truly need confidentiality for the stored data. For general caches, it’s usually not necessary. For truly sensitive fields, prefer secure storage, or store an encryption key in secure storage and use it to encrypt a specific Hive box.
Q: How do I clear all local data on logout?
A: Delete tokens from secure storage, clear sensitive boxes from Hive (or delete the entire box), and reset relevant preferences. Wrap this into a single “signOutAndWipeLocalData” use case to keep the logic in one place.
Conclusion
Think of shared_preferences for simple settings, Hive for structured local data, and flutter_secure_storage for secrets. When you combine them deliberately—preferences, cache, and secure keys—you get fast, offline-friendly apps that still treat user data with respect. In later deep dives, you can refine each layer, but this separation of responsibilities will already keep your architecture clean.
Updated: 2025-11-10


Comment