All Guides
    API & Data
    Database
    Hive
    Local Storage
    Persistence

    Local Storage with Hive

    Published April 2026

    Introduction

    In the modern mobile app ecosystem, user experience is highly dependent on offline capabilities. Users expect your application to load instantly, preserve their configurations, and remain functional even when their device loses internet connectivity. To achieve this, you must store data locally on the user's device.

    For simple key-value configurations (like saving a theme preference or a login token), the standard shared_preferences package is sufficient. For relational data structures, you might use SQLite via the sqflite package. However, SQLite requires writing SQL schemas and database helper classes, which adds code complexity.

    To simplify offline storage, Flutter developers use Hive, a lightweight, blazing-fast, NoSQL database written entirely in Dart. Hive stores data as simple key-value pairs in tables called "boxes." It requires zero native database configurations and supports custom object serialization out of the box. In this guide, you will learn NoSQL persistence concepts, register custom object serializers, and build a local database manager.


    Prerequisites

    Before implementing local storage with Hive, ensure you have:

    • A solid grasp of Dart classes and serialization.
    • Read our Fetching API Data in Flutter guide.
    • Added the required Hive packages to your pubspec.yaml dependencies:
      dependencies:
        hive: ^2.2.3
        hive_flutter: ^1.1.0
      
      dev_dependencies:
        hive_generator: ^2.0.1
        build_runner: ^2.4.8
      

    Core Explanation: Boxes & TypeAdapters

    Hive is a NoSQL database that stores data inside files called Boxes.

      Flutter Application               Hive Engine                Device Disk
    ┌─────────────────────┐       ┌─────────────────────┐     ┌───────────────────┐
    │                     │       │   TypeAdapter       │     │                   │
    │   Custom Model      ├──────►│  (Binary Encoder)  ├────►│  Local Box File   │
    │  (Dart Object instance)     └─────────────────────┘     │  (binary format)  │
    │                     │◄──────────────────────────────────┤                   │
    └─────────────────────┘                                   └───────────────────┘
    

    1. What is a Hive Box?

    A Hive Box is a NoSQL storage table. Unlike SQL databases that use strict schemas with columns and data rows, a Box is a flexible collection of key-value pairs. Keys can be strings or integers, and values can be primitive types (like strings, booleans, or lists) or custom objects.

    2. TypeAdapters (Custom Object Serialization)

    By default, Hive only knows how to write primitive data types. To store a custom Dart object (like a Task or User model), you must register a TypeAdapter. The adapter acts as a binary encoder, converting your custom Dart object into a binary stream that Hive can write to disk, and vice versa.

    To create a TypeAdapter, you annotate your Dart model class with @HiveType and @HiveField and run Flutter's code generator:

    • @HiveType(typeId: 0): Declares the class as serializable. The typeId must be unique across your application.
    • @HiveField(0): Annotates a specific class property. Each field must have a unique index number.
    flutter pub run build_runner build
    

    Running this command compiles the annotations and generates the corresponding g.dart file containing the binary encoder logic.


    Practical Example: Composing a Task Database Manager

    Let's write a complete, compiler-ready database manager class that initializes Hive, registers a custom LocalTask adapter, and executes CRUD (Create, Read, Update, Delete) operations.

    Note: In a real project, the LocalTask model class and the generator code live in separate files. This example combines them to illustrate the complete database lifecycle.

    import 'package:flutter/material.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    
    // ==========================================
    // CUSTOM HIVE MODEL (Task Object)
    // ==========================================
    // Normally, the generator uses: part 'task.g.dart';
    @HiveType(typeId: 1)
    class LocalTask extends HiveObject {
      @HiveField(0)
      final String id;
    
      @HiveField(1)
      String title;
    
      @HiveField(2)
      bool isCompleted;
    
      LocalTask({
        required this.id,
        required this.title,
        this.isCompleted = false,
      });
    }
    
    // ==========================================
    // DATABASE MANAGER SERVICE
    // ==========================================
    class TaskDatabaseService {
      // Constants for our database configuration
      static const String _taskBoxName = 'tasks_box';
    
      // 1. Initialize Hive on application startup
      static Future<void> initializeDatabase() async {
        // Initializes Hive and points it to the correct local directory on mobile/desktop
        await Hive.initFlutter();
        
        // Register our custom TypeAdapter
        // In a real build, register task adapter generated class:
        // Hive.registerAdapter(LocalTaskAdapter());
        
        // Open the tasks box so it is ready for queries
        await Hive.openBox<LocalTask>(_taskBoxName);
      }
    
      // Get a reference to our open tasks box
      Box<LocalTask> get _taskBox => Hive.box<LocalTask>(_taskBoxName);
    
      // ------------------------------------------
      // CRUD ACTIONS
      // ------------------------------------------
    
      // CREATE: Add a new task to the box
      Future<void> createTask(LocalTask task) async {
        // We use the task id as the key for clean lookup
        await _taskBox.put(task.id, task);
      }
    
      // READ: Retrieve all tasks from the box
      List<LocalTask> readAllTasks() {
        return _taskBox.values.toList();
      }
    
      // UPDATE: Update task properties
      Future<void> updateTask(LocalTask task) async {
        // Because LocalTask extends HiveObject, it provides a save() method
        // that automatically updates its entry inside the box.
        await task.save();
      }
    
      // DELETE: Delete a task from the box
      Future<void> deleteTask(String taskId) async {
        await _taskBox.delete(taskId);
      }
    }
    
    // ==========================================
    // UI SCREEN DISPLAY
    // ==========================================
    class LocalTasksScreen extends StatefulWidget {
      const LocalTasksScreen({super.key});
    
      @override
      State<LocalTasksScreen> createState() => _LocalTasksScreenState();
    }
    
    class _LocalTasksScreenState extends State<LocalTasksScreen> {
      final TaskDatabaseService _dbService = TaskDatabaseService();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Offline Tasks Planner'),
            centerTitle: true,
          ),
          // ValueListenableBuilder automatically listens to database changes
          // and rebuilds the UI whenever items are added, updated, or deleted.
          body: ValueListenableBuilder<Box<LocalTask>>(
            valueListenable: Hive.box<LocalTask>('tasks_box').listenable(),
            builder: (context, box, child) {
              final tasks = box.values.toList();
    
              if (tasks.isEmpty) {
                return const Center(
                  child: Text('No tasks saved. Click "+" to add one.'),
                );
              }
    
              return ListView.builder(
                padding: const EdgeInsets.all(16.0),
                itemCount: tasks.length,
                itemBuilder: (context, index) {
                  final task = tasks[index];
                  return Card(
                    child: ListTile(
                      title: Text(
                        task.title,
                        style: TextStyle(
                          decoration: task.isCompleted
                              ? TextDecoration.lineThrough
                              : TextDecoration.none,
                        ),
                      ),
                      leading: Checkbox(
                        value: task.isCompleted,
                        onChanged: (value) async {
                          task.isCompleted = value ?? false;
                          await _dbService.updateTask(task);
                        },
                      ),
                      trailing: IconButton(
                        icon: const Icon(Icons.delete, color: Colors.red),
                        onPressed: () async {
                          await _dbService.deleteTask(task.id);
                        },
                      ),
                    ),
                  );
                },
              );
            },
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              final uniqueId = DateTime.now().millisecondsSinceEpoch.toString();
              final newTask = LocalTask(
                id: uniqueId,
                title: 'New Offline Task #$uniqueId',
              );
              await _dbService.createTask(newTask);
            },
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

    Code Explanation:

    1. Hive.initFlutter(): Initializes the database engine and configures the default path locations.
    2. HiveObject: Extending HiveObject gives our model access to convenience methods like save() and delete(), simplifying update logic.
    3. ValueListenableBuilder: Subscribes to the Hive box, automatically redrawing the list view whenever tasks are modified without needing a state manager.
    4. _taskBox.put(key, value): Writes the key-value pair to disk. If the key already exists, Hive overwrites the value.

    Common Mistakes

    1. Forgetting to Register TypeAdapters

    Attempting to write or read custom objects without registering their type adapter first will crash your application with a HiveError: Cannot write, unknown type... error.

    • The Fix: Always call Hive.registerAdapter() before opening boxes.

    2. Opening Boxes Multiple Times

    Opening a box inside every build method or request function wastes CPU cycles and can lead to file lock errors.

    • The Fix: Open boxes once on app startup (e.g. inside main()) and retrieve references using Hive.box('box_name').

    3. Modifying Field Index Numbers

    Changing the index numbers in @HiveField(index) annotations on an active schema will corrupt your local database files and crash the app for existing users.

    • The Fix: Treat field index numbers as immutable. If you add new fields, assign them a new, incremented index number.

    Best Practices

    • Initialize in main(): Run all database initialization, adapter registration, and box opening routines inside your app's main() method before executing runApp().
    • Encrypt Sensitive Boxes: Use Hive's built-in AES encryption features (Hive.generateSecureKey()) to secure boxes containing sensitive user data (like passwords or authentication tokens).
    • Keep Model Schemas Small: Store only lightweight parameters in Hive boxes. For large media files (like photos or audio), save the file to disk and store only the file path string in the database.

    Related Concepts

    Continue mastering persistence configurations by exploring these guides:


    Summary

    • Hive is a fast, NoSQL database written in Dart that stores data as key-value pairs in "boxes."
    • TypeAdapters are binary encoders that allow Hive to serialize custom Dart objects.
    • Always register adapters and open boxes once during application startup.
    • Use ValueListenableBuilder to automatically update your UI when database boxes are modified.

    Next Steps

    1. Previous Guide: Using Dio for HTTP Requests
    2. Next Guide: Riverpod Fundamentals
    3. Related Guide: Managing State with Provider

    Ready to take the next step?

    Start our free Flutter course and build real apps today.

    Start Learning Now