Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter

Flutter

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

PackageBest forData modelSecurityPlatforms
shared_preferencesSmall user settings (theme, flags)Simple key–value (primitives only)Not encryptedAndroid, iOS, Web, Desktop
HiveLocal databases / offline cachesBoxes with typed objectsOptional encryptionAndroid, iOS, Web, Desktop
flutter_secure_storageSecrets (tokens, keys, PINs)Key–value for small secretsEncrypted via Keychain / KeystoreAndroid, 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
    Do not store secrets here.
  • 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
    This should be the only place where secrets live on-device.

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

Copied title and URL