Handling local data persistence in Flutter with Hive - LogRocket Blog (2024)

Storing data locally and persisting between app launches is one of the fundamental concepts of any mobile app development process. Almost every app requires that you handle data — from storing customer information for a food delivery app, to the number of points scored in a game or a simple value to understand whether the user has turned on dark mode during their last visit.

Handling local data persistence in Flutter with Hive - LogRocket Blog (1)

Flutter provides many local data persistence options for developers to choose from. shared_preferences is a good package for storing small key-value pairs locally, and sqflite, the SQLite package for Flutter, is a good choice when you’re dealing with strong relational data that requires you to handle complex relationships in the database.

But if you want a fast and secure local database with no native dependencies that also runs on Flutter web (😉), then Hive is a pretty good choice.

In this article, you will learn how to get started with Hive before we build a simple app using Flutter. We will also look into a concept that allows you to handle simple relational data in Hive.

Why Hive?

Let’s first take a look at why you should choose Hive over the other solutions available for persisting data locally in Flutter.

Hive is a lightweight and fast key-value database solution that is cross-platform (runs on mobile, desktop, and web) and is written in pure Dart. This gives it an instant advantage over sqflite, which doesn’t support Flutter web — Hive has no native dependencies, so it runs seamlessly on the web.

Below is a graph that benchmarks Hive against other similar database solutions:

Handling local data persistence in Flutter with Hive - LogRocket Blog (2)

Hive also allows you to store custom classes using TypeAdapters. We will take a look at this in more detail later in the article.

Getting started with Hive

Let’s build a basic app where our users’ details are stored and where add, read, update, and delete operations on the data can be performed.

Handling local data persistence in Flutter with Hive - LogRocket Blog (3)

Create a new Flutter project using the following command:

flutter create hive_demo

You can open the project using your favorite IDE, but for this example, I’ll be using VS Code:

code hive_demo

Add the Hive and hive_flutter packages to your pubspec.yaml file:

dependencies: hive: ^2.1.0 hive_flutter: ^1.1.0

Replace the content of your main.dart file with:

import 'package:flutter/material.dart';main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Hive Demo', theme: ThemeData( primarySwatch: Colors.purple, ), debugShowCheckedModeBanner: false, home: InfoScreen(), ); }}

The InfoScreen will display the details of the user — we will take a look at it in a moment. Before that, let’s understand an important concept used by Hive.

Understanding boxes in Hive

Hive uses the concept of “boxes” for storing data in the database. A box is similar to a table on an SQL database, except that boxes lack a strict structure. This means boxes are flexible and can only handle simple relationships between data.

Before accessing the data stored inside a box, you must open it. This loads the entire content of the box from local storage into the memory so that any data present inside the box can be easily accessed.

The following example shows how to open a box named peopleBox and get the parameter name from it:

var box = await Hive.openBox('peopleBox');String name = box.get('name');

Other than the normal boxes, there are two more varieties of boxes:

Using a lazy box in Hive

Normal Hive boxes load the entire content of the box into the memory as it’s opened. But this might not be a good way to load a box if there’s a massive amounts of data inside it.

On opening a lazy box, only the keys are read and stored in memory. You can use the key to retrieve its respective value from the box.

You can use a lazy box like this:

var lazyBox = await Hive.openLazyBox('hugePeopleBox');String name = await lazyBox.get('name');

Note: for accessing a value from a normal box, you have to get it without using await. But, in a lazy box, you have to use await because the content is not present in the memory — only its respective key is available.

Storing sensitive information with encrypted boxes

You might need to store some sensitive information using Hive, and this is where an encrypted box comes to the rescue. Hive comes with support for AES-256 encryption out of the box, along with a helper function for generating an encryption key using the Fortuna algorithm.

Over 200k developers use LogRocket to create better digital experiencesLearn more →

To store the encryption key securely in the device, it’s recommended to use the flutter_secure_storage package.

Here’s an example of creating and opening an encrypted box:

const secureStorage = FlutterSecureStorage();final encryprionKey = await secureStorage.read(key: 'key');if (encryprionKey == null) { final key = Hive.generateSecureKey(); await secureStorage.write( key: 'key', value: base64UrlEncode(key), );}final key = await secureStorage.read(key: 'key');final encryptionKey = base64Url.decode(key!);print('Encryption key: $encryptionKey');await Hive.openBox( 'securedBox', encryptionCipher: HiveAesCipher(encryptionKey),);

To fetch and store data in this box, the following methods can be used:

final encryptedBox = Hive.box('securedBox');_getData() { setState(() { data = encryptedBox.get('secret'); }); log('Fetched data');}_putData() async { await encryptedBox.put('secret', 'Test secret key'); log('Stored data');}

The complete example of using a Hive encrypted box is available here.

Initializing Hive

Before moving on to the CRUD operations of the database, initialize Hive and open a box that will be used for storing the data.

Hive should be initialized before we load any boxes, so it’s best to initialize it inside the main() function of your Flutter app to avoid any errors. Note that if you are using Hive in a non-Flutter, pure Dart app, use Hive.init() to initialize Hive.

main() async { // Initialize hive await Hive.initFlutter(); runApp(MyApp());}

Make the main function asynchronous and use await to initialize Hive.

Now, open a Hive box. If you plan to use multiple boxes in your project, note that you should open a box before using it.

In this app, we’ll use a single box we’ll open just after Hive completes initialization.

main() async { // Initialize hive await Hive.initFlutter(); // Open the peopleBox await Hive.openBox('peopleBox'); runApp(MyApp());}

We are now ready to perform CRUD operations on the local database.

More great articles from LogRocket:

  • Don't miss a moment with The Replay, a curated newsletter from LogRocket
  • Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
  • Use React's useEffect to optimize your application's performance
  • Switch between multiple versions of Node
  • Discover how to use the React children prop with TypeScript
  • Explore creating a custom mouse cursor with CSS
  • Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Performing CRUD operations

We will define the basic CRUD operations in the InfoScreen StatefulWidget. The structure of this class will be as follows:

import 'package:flutter/material.dart';import 'package:hive/hive.dart';class InfoScreen extends StatefulWidget { @override _InfoScreenState createState() => _InfoScreenState();}class _InfoScreenState extends State<InfoScreen> { late final Box box; @override void initState() { super.initState(); // Get reference to an already opened box box = Hive.box('peopleBox'); } @override void dispose() { // Closes all Hive boxes Hive.close(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); }}

First, we retrieve a reference to the box inside the initState() method that we had opened earlier. You should always close the opened boxes after you are done using them and before closing the application.

As we currently only require the box inside this widget, we can close the box inside the dispose() method of this class.

Let’s create some methods for performing the CRUD operations.

class _InfoScreenState extends State<InfoScreen> { late final Box box; _addInfo() async { // Add info to people box } _getInfo() { // Get info from people box } _updateInfo() { // Update info of people box } _deleteInfo() { // Delete info from people box } // ...}

Now we’ll build a very basic UI so that we can test out whether the operations are working properly.

class _InfoScreenState extends State<InfoScreen> { // ... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('People Info'), ), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( onPressed: _addInfo, child: Text('Add'), ), ElevatedButton( onPressed: _getInfo, child: Text('Get'), ), ElevatedButton( onPressed: _updateInfo, child: Text('Update'), ), ElevatedButton( onPressed: _deleteInfo, child: Text('Delete'), ), ], ), ), ); }}

The app will look like this:

Handling local data persistence in Flutter with Hive - LogRocket Blog (6)

Storing data in Hive

If you need to store data, you can use the reference to the Hive box and call put() on it. This method accepts a key-value pair.

// Add info to people box_addInfo() async { // Storing key-value pair box.put('name', 'John'); box.put('country', 'Italy'); print('Info added to box!');}

Here, we have stored two key-value pairs, the Name of the person and their Home Country.

Hive also supports integer keys, so you can use auto-incrementing keys. This can be useful if you are storing multiple values (kinda similar to a list) and want to retrieve by their indices. You can store like this:

box.add('Linda'); // index 0, key 0box.add('Dan'); // index 1, key 1

Retrieving data

To read data, you can use the get() method on the box object. You just have to provide the key for retrieving its value.

// Read info from people box_getInfo() { var name = box.get('name'); var country = box.get('country'); print('Info retrieved from box: $name ($country)');}

If you are using auto-incrementing values, you can read using the index, like this:

box.getAt(0); // retrieves the value with index 0box.getAt(1); // retrieves the value with index 1

Updating data

To update the data of a particular key, you can use the same put() method that you originally used to store the value. This will update the value present at that key with the newly provided value.

// Update info of people box_updateInfo() { box.put('name', 'Mike'); box.put('country', 'United States'); print('Info updated in box!');}

If you are using auto-incrementing values, you can use the putAt() method for updating the value present at a particular index.

box.putAt(0, 'Jenifer');

Deleting data

For deleting data, you can use the delete() method by providing the key.

// Delete info from people box_deleteInfo() { box.delete('name'); box.delete('country'); print('Info deleted from box!');}

This will delete the values present at those particular keys. Now, if you try to call the get() method using these keys, it will return null values.

If you are using auto-incrementing values, you can use deleteAt() method by providing the index.

box.deleteAt(0);

Using custom objects with TypeAdapter

In general, Hive supports all primitive types like List, Map, DateTime, and Uint8List. But sometimes you may need to store custom model classes that make data management easier.

To do this, you can take advantage of a TypeAdapter, which generates the to and from binary methods.

TypeAdapters can either be written manually or generated automatically. It’s always better to use code generation to generate the required methods because it helps to prevent any mistakes that might occur while writing manually (and also it’s faster).

The model class that we’ll be using for storing Person data is:

class Person { final String name; final String country; Person({ required this.name, required this.country, });}

Generating the Hive adapter

You will need to add some dependencies to generate the TypeAdapter for Hive. Add the following to your pubspec.yaml file:

dev_dependencies: hive_generator: ^1.1.2 build_runner: ^2.1.8

Annotate the model class to use code generation:

import 'package:hive/hive.dart';part 'people.g.dart';@HiveType(typeId: 1)class People { @HiveField(0) final String name; @HiveField(1) final String country; People({ required this.name, required this.country, });}

You can then trigger code generation using the following command:

flutter packages pub run build_runner build

Registering the TypeAdapter

You should register the TypeAdapter before opening the box that is using it — otherwise, it will produce an error. As we are just using a single box and have opened it inside the main() function, we have to register the adapter before that.

main() async { // Initialize hive await Hive.initFlutter(); // Registering the adapter Hive.registerAdapter(PersonAdapter()); // Opening the box await Hive.openBox('peopleBox'); runApp(MyApp());}

Now, you can directly perform database operations using this custom class.

Building the final app

The final app will mainly comprise three screens:

  1. AddScreen: for storing the user’s information on the database
  2. InfoScreen: for showing the user’s information that is present in the Hive database, and a button for deleting the user’s data
  3. UpdateScreen: for updating the user’s information on the database

You do not need to modify the main.dart file containing the MyApp widget and the main() function.

AddScreen

The AddScreen will display a form for taking the user’s data as inputs. In our case, we will input just two values, Name and Home Country. At the bottom will be a button for sending the data to Hive.

Handling local data persistence in Flutter with Hive - LogRocket Blog (7)

The code for the AddScreen is:

class AddScreen extends StatefulWidget { @override _AddScreenState createState() => _AddScreenState();}class _AddScreenState extends State<AddScreen> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text('Add Info'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: AddPersonForm(), ), ); }}

AddPersonForm is the main widget where the UI for the form is created. It also contains the Hive storage functionality.

The basic structure of the widget will look like this:

class AddPersonForm extends StatefulWidget { const AddPersonForm({Key? key}) : super(key: key); @override _AddPersonFormState createState() => _AddPersonFormState();}class _AddPersonFormState extends State<AddPersonForm> { late final Box box; @override void initState() { super.initState(); // Get reference to an already opened box box = Hive.box('peopleBox'); } @override Widget build(BuildContext context) { return Container(); }}

We have retrieved a reference to the box inside the initState() method. Now, we have to define a global key for the form and add some text editing controllers.

class _AddPersonFormState extends State<AddPersonForm> { final _nameController = TextEditingController(); final _countryController = TextEditingController(); final _personFormKey = GlobalKey<FormState>(); // ...}

Define a method for storing data to Hive and add a text field validator:

class _AddPersonFormState extends State<AddPersonForm> { // ... // Add info to people box _addInfo() async { Person newPerson = Person( name: _nameController.text, country: _countryController.text, ); box.add(newPerson); print('Info added to box!'); } String? _fieldValidator(String? value) { if (value == null || value.isEmpty) { return 'Field can\'t be empty'; } return null; } // ...}

The code for the UI is as follows:

class _AddPersonFormState extends State<AddPersonForm> { // ... @override Widget build(BuildContext context) { return Form( key: _personFormKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Name'), TextFormField( controller: _nameController, validator: _fieldValidator, ), SizedBox(height: 24.0), Text('Home Country'), TextFormField( controller: _countryController, validator: _fieldValidator, ), Spacer(), Padding( padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 24.0), child: Container( width: double.maxFinite, height: 50, child: ElevatedButton( onPressed: () { if (_personFormKey.currentState!.validate()) { _addInfo(); Navigator.of(context).pop(); } }, child: Text('Add'), ), ), ), ], ), ); }}

UpdateScreen in Hive

The UpdateScreen will be similar to the AddScreen, but here we’ll pass the Person object to show the current value in the text fields.

Handling local data persistence in Flutter with Hive - LogRocket Blog (8)

The code for this screen will be:

class UpdateScreen extends StatefulWidget { final int index; final Person person; const UpdateScreen({ required this.index, required this.person, }); @override _UpdateScreenState createState() => _UpdateScreenState();}class _UpdateScreenState extends State<UpdateScreen> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text('Update Info'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: UpdatePersonForm( index: widget.index, person: widget.person, ), ), ); }}

The only difference in the UpdatePersonForm widget is that it will contain a method for updating the value present in the Hive database.

class _UpdatePersonFormState extends State<UpdatePersonForm> { late final _nameController; late final _countryController; late final Box box; // ... // Update info of people box _updateInfo() { Person newPerson = Person( name: _nameController.text, country: _countryController.text, ); box.putAt(widget.index, newPerson); print('Info updated in box!'); } @override void initState() { super.initState(); // Get reference to an already opened box box = Hive.box('peopleBox'); // Show the current values _nameController = TextEditingController(text: widget.person.name); _countryController = TextEditingController(text: widget.person.country); } @override Widget build(BuildContext context) { return Form( // ... ); }}

InfoScreen

The InfoScreen will display the Person data stored in Hive. Basically, the read operation will be performed here.

Handling local data persistence in Flutter with Hive - LogRocket Blog (9)

Hive provides a widget called ValueListenableBuilder that only refreshes when any value inside the database is modified.

This screen will contain some additional functionalities:

  • Tapping the Delete button next to each list item will remove the user’s data from the database
  • Tapping each list item will navigate to the UpdateScreen
  • Tapping the floating action button in the bottom right will bring you to the AddScreen

The code for this screen is:

class InfoScreen extends StatefulWidget { @override _InfoScreenState createState() => _InfoScreenState();}class _InfoScreenState extends State<InfoScreen> { late final Box contactBox; // Delete info from people box _deleteInfo(int index) { contactBox.deleteAt(index); print('Item deleted from box at index: $index'); } @override void initState() { super.initState(); // Get reference to an already opened box contactBox = Hive.box('peopleBox'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('People Info'), ), floatingActionButton: FloatingActionButton( onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => AddScreen(), ), ), child: Icon(Icons.add), ), body: ValueListenableBuilder( valueListenable: contactBox.listenable(), builder: (context, Box box, widget) { if (box.isEmpty) { return Center( child: Text('Empty'), ); } else { return ListView.builder( itemCount: box.length, itemBuilder: (context, index) { var currentBox = box; var personData = currentBox.getAt(index)!; return InkWell( onTap: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => UpdateScreen( index: index, person: personData, ), ), ), child: ListTile( title: Text(personData.name), subtitle: Text(personData.country), trailing: IconButton( onPressed: () => _deleteInfo(index), icon: Icon( Icons.delete, color: Colors.red, ), ), ), ); }, ); } }, ), ); }}

Congratulations 🥳, you have completed your Flutter app using Hive as the local persistent database.

A demo of the final app is shown below:

Handling local data persistence in Flutter with Hive - LogRocket Blog (10)

Conclusion

This article covers most of the important, basic concepts of Hive. There are a few more things you can do with the Hive database, including storing simple relational data. Simple relationships between data can be handled using HiveList, but if you are storing any sensitive data in Hive, then you should use the encrypted box.

In a nutshell, Hive is one of the best choices you have for local data persistence in Flutter, especially considering that it’s blazing fast and supports almost all platforms.

Thank you for reading the article! If you have any suggestions or questions about the article or examples, feel free to connect with me on Twitter or LinkedIn. You can also find the repository of the sample app on my GitHub.

Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to getan app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, notserver-side

    • npm
    • Script tag
    $ npm i --save logrocket // Code:import LogRocket from 'logrocket'; LogRocket.init('app/id'); 
    // Add to your HTML:<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script><script>window.LogRocket && window.LogRocket.init('app/id');</script> 
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin

Get started now

Handling local data persistence in Flutter with Hive - LogRocket Blog (2024)
Top Articles
Latest Posts
Article information

Author: Arline Emard IV

Last Updated:

Views: 5947

Rating: 4.1 / 5 (72 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Arline Emard IV

Birthday: 1996-07-10

Address: 8912 Hintz Shore, West Louie, AZ 69363-0747

Phone: +13454700762376

Job: Administration Technician

Hobby: Paintball, Horseback riding, Cycling, Running, Macrame, Playing musical instruments, Soapmaking

Introduction: My name is Arline Emard IV, I am a cheerful, gorgeous, colorful, joyous, excited, super, inquisitive person who loves writing and wants to share my knowledge and understanding with you.