Flutter & Hive Database: CRUD Example (2022)

Last updated on January 7, 2022 A Goodman Loading... 8 comments

Hive is a lightweight key-value database that can be used to store data locally in mobile, desktop, and web applications. It is written in pure Dart and works very well with Flutter. CRUD means create, read, update, and delete, the four essential operations of persistent storage.

In this article, we’re going to build a full-feature and quite useful app with Hive and Flutter.

Overview

The basics

Some of the great advantages of Hive:

  • Easy to use, just like Dart maps. You don’t have to write long and complicated queries to query data.
  • High flexibility. You can change your data structure easily because Hive doesn’t use tables like SQLite.
  • No native dependencies
  • Fast and can handle a large amount of data.
  • Supports primitives (string, number, list, map, etc) and Dart objects (with the help of adapters)

In order to use Hive in Flutter, you need to install the hive plugin and the hive_flutter plugin.

You can initialize Hive in the main() function of your app:

void main() async {
  /*... */
  await Hive.initFlutter();
  /*...*/
}

All data stored in Hive is organized in boxes. A box can be compared to a table in SQL, but it does not have a structure and can contain anything. You can open a box like this:

await Hive.openBox('shopping_box'); // the name is totally up to you

Once a box is open, all its data from the local storage is loaded into memory for immediate access. You can retrieve data synchronously without using async/await:

final myBox = Hive.box('shopping_box');
final something = myBox.get('my_key');

Adding a new item to the box:

await myBox.put('some_key', someValue);

Updating an existing item in the box:

await myBox.put('some_key', someValue);

Deleting an item in the box:

await myBox.delete('some_key');

Storing a list of items with auto-increment keys

You can save a list of items with Hive easily by using the add() method. In this case, the keys are auto-generated and auto-increment, starting from 0, then 1, 2, 3, and so on. For example:

final listBox = Hive.box('my_list');
int newKey = await listBox.add(someData);

You can retrieve all keys and values from the box like this:

final keys = listBox.keys; 
final values = listBox.values;

You can use loops with keys and values as needed. In cases where you prefer to create and set your own keys (by using the put() method as mentioned earlier), using DateTime.now().toString() is a good idea. You can also find some other useful techniques in this article: Flutter & Dart: 3 Ways to Generate Random Strings.

The Complete Example

App Preview

Imagine your wife or your mom is cooking a big dinner and she asks you to go to a store nearby to get her some ingredients. It’s hard to memorize those things, so you seek a pen and paper to scribble them down. However, you cannot find what you need. Here the rescuer comes in. It’s a shopping list app that can run totally offline without an internet connection.

The shopping list app we are talking about has a floating button. When this button is pressed, a bottom sheet with text fields will appear so that you add a new item (including its name and quantity) and store it in the Hive database.

All saved items are displayed in a list view. Associating with each item is an edit button and a delete button, used for updating and deleting the item, respectively.

Here’s how our app works:

The Code

Installing hive and hive_flutter by running:

dart pub add hive

And:

flutter pub add hive_flutter

The full source code in main.dart with detailed explanations:

// main.dart
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Hive.initFlutter();
  // await Hive.deleteBoxFromDisk('shopping_box');
  await Hive.openBox('shopping_box');

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'KindaCode.com',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  List<Map<String, dynamic>> _items = [];

  final _shoppingBox = Hive.box('shopping_box');

  @override
  void initState() {
    super.initState();
    _refreshItems(); // Load data when app starts
  }

  // Get all items from the database
  void _refreshItems() {
    final data = _shoppingBox.keys.map((key) {
      final value = _shoppingBox.get(key);
      return {"key": key, "name": value["name"], "quantity": value['quantity']};
    }).toList();

    setState(() {
      _items = data.reversed.toList();
      // we use "reversed" to sort items in order from the latest to the oldest
    });
  }

  // Create new item
  Future<void> _createItem(Map<String, dynamic> newItem) async {
    await _shoppingBox.add(newItem);
    _refreshItems(); // update the UI
  }

  // Retrieve a single item from the database by using its key
  // Our app won't use this function but I put it here for your reference
  Map<String, dynamic> _readItem(int key) {
    final item = _shoppingBox.get(key);
    return item;
  }

  // Update a single item
  Future<void> _updateItem(int itemKey, Map<String, dynamic> item) async {
    await _shoppingBox.put(itemKey, item);
    _refreshItems(); // Update the UI
  }

  // Delete a single item
  Future<void> _deleteItem(int itemKey) async {
    await _shoppingBox.delete(itemKey);
    _refreshItems(); // update the UI

    // Display a snackbar
    ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('An item has been deleted')));
  }

  // TextFields' controllers
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _quantityController = TextEditingController();

  // This function will be triggered when the floating button is pressed
  // It will also be triggered when you want to update an item
  void _showForm(BuildContext ctx, int? itemKey) async {
    // itemKey == null -> create new item
    // itemKey != null -> update an existing item

    if (itemKey != null) {
      final existingItem =
          _items.firstWhere((element) => element['key'] == itemKey);
      _nameController.text = existingItem['name'];
      _quantityController.text = existingItem['quantity'];
    }

    showModalBottomSheet(
        context: ctx,
        elevation: 5,
        isScrollControlled: true,
        builder: (_) => Container(
              padding: EdgeInsets.only(
                  bottom: MediaQuery.of(ctx).viewInsets.bottom,
                  top: 15,
                  left: 15,
                  right: 15),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  TextField(
                    controller: _nameController,
                    decoration: const InputDecoration(hintText: 'Name'),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                  TextField(
                    controller: _quantityController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(hintText: 'Quantity'),
                  ),
                  const SizedBox(
                    height: 20,
                  ),
                  ElevatedButton(
                    onPressed: () async {
                      // Save new item
                      if (itemKey == null) {
                        _createItem({
                          "name": _nameController.text,
                          "quantity": _quantityController.text
                        });
                      }

                      // update an existing item
                      if (itemKey != null) {
                        _updateItem(itemKey, {
                          'name': _nameController.text.trim(),
                          'quantity': _quantityController.text.trim()
                        });
                      }

                      // Clear the text fields
                      _nameController.text = '';
                      _quantityController.text = '';

                      Navigator.of(context).pop(); // Close the bottom sheet
                    },
                    child: Text(itemKey == null ? 'Create New' : 'Update'),
                  ),
                  const SizedBox(
                    height: 15,
                  )
                ],
              ),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('KindaCode.com'),
      ),
      body: _items.isEmpty
          ? const Center(
              child: Text(
                'No Data',
                style: TextStyle(fontSize: 30),
              ),
            )
          : ListView.builder(
              // the list of items
              itemCount: _items.length,
              itemBuilder: (_, index) {
                final currentItem = _items[index];
                return Card(
                  color: Colors.orange.shade100,
                  margin: const EdgeInsets.all(10),
                  elevation: 3,
                  child: ListTile(
                      title: Text(currentItem['name']),
                      subtitle: Text(currentItem['quantity'].toString()),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          // Edit button
                          IconButton(
                              icon: const Icon(Icons.edit),
                              onPressed: () =>
                                  _showForm(context, currentItem['key'])),
                          // Delete button
                          IconButton(
                            icon: const Icon(Icons.delete),
                            onPressed: () => _deleteItem(currentItem['key']),
                          ),
                        ],
                      )),
                );
              }),
      // Add new item button
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showForm(context, null),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Conclusion

You’ve learned the fundamentals of the Hive database in Flutter. You’ve also examined a small but full-feature app that uses Hive to store data offline. If you’d like to explore more about persisting data and other interesting things in Flutter, take a look at the following articles:

You can also check out our Flutter category page or Dart category page for the latest tutorials and examples.

Subscribe
Notify of
guest
8 Comments
Inline Feedbacks
View all comments
Hoang Nguyen
Hoang Nguyen
1 month ago

Thanks a lot, A Good Man, I added a Search icon and a Text field on the App Bar, and spent hours but cannot write the right method to make it search. Please help, I am new.

bet
bet
1 month ago

If i dont want to repeated item how can i create condition to this code? Please help me, I’d appreciate it .

bet
bet
1 month ago
Reply to  A Goodman

Last day i write like _nameController.text … but it didnt work. I also tried to contains() but again it didnt work. I understand the code but i cant understand how can i create condition for duplicate. I’m beginner for hive and flutter 🙁 sorry if i bother you

Sadikul
Sadikul
3 months ago

How to visualize a hive file? Just Like we do in MySQL…

imtiaz
imtiaz
7 months ago

Great 👍
Helped a lot ❤️

PkYadav
PkYadav
7 months ago

Well done buddy good work

Related Articles