image

10 Dec 2025

9K

35K

Building a ToDo App with Hive in Flutter

Building a ToDo application is a classic way to learn a new framework or database, and Flutter with Hive offers an excellent combination for creating a fast, reliable, and user-friendly local data storage solution. Hive is a lightweight and blazing-fast key-value database written purely in Dart, making it a perfect choice for Flutter applications that require local persistence without the overhead of more complex databases like SQLite.

Why Hive for a ToDo App?

Hive stands out for several reasons, especially for a simple application like a ToDo list:

  • Speed: It's incredibly fast, often outperforming other local databases.
  • Simplicity: Its API is straightforward, making it easy to learn and implement.
  • Lightweight: No native dependencies, making your app smaller and faster to compile.
  • Type Safety: Supports custom objects through TypeAdapters, ensuring type-safe data storage.
  • Reactive: Integrates seamlessly with Flutter's reactive paradigms using ValueListenableBuilder for real-time UI updates.

Project Setup

First, create a new Flutter project:


flutter create hive_todo_app
cd hive_todo_app

Next, add the necessary dependencies to your pubspec.yaml file. We'll need hive for the core database, hive_flutter for Flutter-specific utilities (like ValueListenableBuilder), path_provider to get the application's document directory, and hive_generator and build_runner for generating type adapters.


dependencies:
  flutter:
    sdk: flutter
  hive: ^2.0.6
  hive_flutter: ^1.1.0
  path_provider: ^2.0.11

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  hive_generator: ^1.1.3
  build_runner: ^2.1.11

After updating pubspec.yaml, run flutter pub get to fetch the dependencies.


flutter pub get

Defining the Data Model

For our ToDo app, each ToDo item will have a title and a boolean indicating if it's completed. We'll create a class TodoItem and annotate it with @HiveType and @HiveField. This tells Hive how to store and retrieve our custom object.

Create a new file lib/models/todo_item.dart:


import 'package:hive/hive.dart';

part 'todo_item.g.dart'; // This file will be generated by hive_generator

@HiveType(typeId: 0) // Unique ID for this type adapter, must be 0-223
class TodoItem extends HiveObject {
  @HiveField(0) // Unique ID for each field within this type
  String title;

  @HiveField(1)
  bool isCompleted;

  TodoItem({required this.title, this.isCompleted = false});
}

After defining the model, generate the type adapter by running:


flutter pub run build_runner build

This command will create a file named todo_item.g.dart in the same directory as todo_item.dart. This file contains the necessary code for Hive to serialize and deserialize your TodoItem objects.

Initializing Hive and Opening a Box

Before using Hive, it needs to be initialized. This is typically done in the main function of your app. We'll also register our TodoItemAdapter and open a "box" (Hive's term for a database table).

Update your lib/main.dart:


import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:hive_todo_app/models/todo_item.dart';
import 'package:hive_todo_app/home_page.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized(); // Required for Flutter services

  final appDocumentDirectory = await getApplicationDocumentsDirectory();
  await Hive.initFlutter(appDocumentDirectory.path);

  // Register the generated adapter
  Hive.registerAdapter(TodoItemAdapter());

  // Open the box for our TodoItem objects
  await Hive.openBox('todoBox');

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hive Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        appBarTheme: const AppBarTheme(
          centerTitle: true,
        ),
      ),
      home: const HomePage(),
    );
  }
}

In the above code:

  • WidgetsFlutterBinding.ensureInitialized() ensures that Flutter's widget binding is initialized before Hive.
  • getApplicationDocumentsDirectory() gets a path to the directory where the app can store data.
  • Hive.initFlutter() initializes Hive with the given path.
  • Hive.registerAdapter(TodoItemAdapter()) tells Hive how to work with our TodoItem class.
  • Hive.openBox('todoBox') opens a box named 'todoBox' specifically for TodoItem objects. This box will hold all our ToDo items.

Building the User Interface and CRUD Operations

Now, let's create the UI for our ToDo list and implement the Create, Read, Update, and Delete (CRUD) operations. We'll use a ValueListenableBuilder to automatically rebuild the UI whenever data in our 'todoBox' changes.

Create a new file lib/home_page.dart:


import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hive_todo_app/models/todo_item.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State {
  final TextEditingController _taskController = TextEditingController();
  late Box _todoBox;

  @override
  void initState() {
    super.initState();
    _todoBox = Hive.box('todoBox'); // Get the opened box
  }

  @override
  void dispose() {
    _taskController.dispose();
    super.dispose();
  }

  // --- CRUD Operations ---

  // Create (Add) a new TodoItem
  void _addTask() {
    if (_taskController.text.isNotEmpty) {
      _todoBox.add(TodoItem(title: _taskController.text));
      _taskController.text = ''; // Clear the input field
      Navigator.of(context).pop(); // Close the dialog
    }
  }

  // Update a TodoItem (e.g., toggle completion status)
  void _updateTask(TodoItem item, bool isCompleted) {
    item.isCompleted = isCompleted;
    item.save(); // Calling save() on a HiveObject persists changes
  }

  // Delete a TodoItem
  void _deleteTask(TodoItem item) {
    item.delete(); // Calling delete() on a HiveObject removes it from the box
  }

  // --- UI Helpers ---

  void _showAddTaskDialog() {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Add New Todo'),
          content: TextField(
            controller: _taskController,
            decoration: const InputDecoration(hintText: 'Enter todo item'),
            autofocus: true,
            onSubmitted: (_) => _addTask(), // Add on enter key
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: _addTask,
              child: const Text('Add'),
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hive Todo App'),
      ),
      body: ValueListenableBuilder(
        valueListenable: _todoBox.listenable(), // Listen for changes in the box
        builder: (context, Box box, _) {
          if (box.values.isEmpty) {
            return const Center(
              child: Text('No todos yet! Add one using the + button.'),
            );
          }
          return ListView.builder(
            itemCount: box.values.length,
            itemBuilder: (context, index) {
              final TodoItem todo = box.getAt(index)!; // Get item by index
              return Card(
                margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                elevation: 2,
                child: ListTile(
                  title: Text(
                    todo.title,
                    style: TextStyle(
                      decoration: todo.isCompleted
                          ? TextDecoration.lineThrough
                          : TextDecoration.none,
                      color: todo.isCompleted ? Colors.grey[600] : Colors.black87,
                    ),
                  ),
                  leading: Checkbox(
                    value: todo.isCompleted,
                    onChanged: (bool? value) {
                      if (value != null) {
                        _updateTask(todo, value);
                      }
                    },
                  ),
                  trailing: IconButton(
                    icon: const Icon(Icons.delete, color: Colors.redAccent),
                    onPressed: () => _deleteTask(todo),
                  ),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddTaskDialog,
        child: const Icon(Icons.add),
      ),
    );
  }
}

In HomePage:

  • _todoBox = Hive.box('todoBox'); retrieves the opened box.
  • _addTask() uses _todoBox.add() to insert a new TodoItem. When you add a HiveObject without specifying a key, Hive automatically assigns one.
  • _updateTask() modifies the isCompleted property of a TodoItem and then calls item.save(). This method, available on HiveObject, persists the changes back to the box.
  • _deleteTask() calls item.delete(), also available on HiveObject, to remove the item from the box.
  • ValueListenableBuilder(valueListenable: _todoBox.listenable(), ...) is the core of reactive UI. It rebuilds its child whenever _todoBox changes (e.g., an item is added, updated, or deleted).
  • Inside the ValueListenableBuilder, we use a ListView.builder to display all items from box.values.

Running the Application

You can now run your ToDo application:


flutter run

You should see a simple ToDo list. You can add new tasks, mark them as complete, and delete them, with all changes persisting locally using Hive.

Conclusion

Building a ToDo app with Flutter and Hive demonstrates the power and simplicity of this combination for local data persistence. Hive offers a highly performant, easy-to-use, and reactive solution that integrates beautifully with Flutter's widget tree through ValueListenableBuilder. This approach is ideal for many Flutter applications that require offline capabilities or efficient local storage without the complexity of a full-fledged relational database. With Hive, managing your application's data becomes a delightful and straightforward experience.

Related Articles

Dec 19, 2025

Building a Widget List with Sticky

Building a Widget List with Sticky Header in Flutter Creating dynamic and engaging user interfaces is crucial for modern applications. One common UI pattern th

Dec 19, 2025

Mastering Transform Scale & Rotate Animations in Flutter

Mastering Transform Scale & Rotate Animations in Flutter Flutter's powerful animation framework allows developers to create visually stunning and highly intera

Dec 19, 2025

Building a Countdown Timer Widget in Flutter

Building a Countdown Timer Widget in Flutter Countdown timers are a fundamental component in many modern applications, ranging from e-commerce platforms indica