Flutter Tutorial – Hive NoSQL Database In 30 Minutes & Hive CRUD | Android, iOS & Web

    In this Flutter tutorial, we’ll show you how to use Hive – a fast and lightweight NoSQL database solution – to store and manage data in your Flutter app. You’ll learn how to set up Hive in your project, create and manage a Hive box, and perform CRUD (Create, Read, Update, Delete) operations on your data. We’ll provide step-by-step instructions and code examples so you can follow along and implement Hive in your own app.

Table of Contents:

Introduction to Hive database

Setting up the project with Hive

Creating and opening a Hive box

Defining and using Hive adapters

Adding and retrieving data from the Hive box

Updating and deleting data in the Hive box

Conclusion and further resources

Introduction:

In this section, we’ll introduce Hive and explain why it’s a good choice for managing data in Flutter apps. We’ll cover the basics of NoSQL databases, and explain how Hive differs from other popular database solutions.

Setting up the project:

Here, we’ll show you how to set up a new Flutter project with the necessary dependencies for using Hive. We’ll cover how to install and configure the hive_flutter and path packages, and how to import them into your project.

name: flutter_hive_database
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
  sdk: '>=2.18.6 <3.0.0'
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  intl:
#  add this two dependency
  hive:
  hive_flutter:
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
#  add this 2 depe.....
  hive_generator:
  build_runner:
flutter:
  uses-material-design: true


Dart

Creating and opening a Hive box:

In this section, we’ll create a new Hive box to store our data. We’ll cover how to define a Hive adapter for our data model, how to initialize the Hive box, and how to close the box when we’re done.

//import thos packages
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main()async {
  WidgetsFlutterBinding.ensureInitialized();
  //init hive
  await Hive.initFlutter();
  Hive.registerAdapter(TransactionAdapter());
  //name of box
  await Hive.openBox<Transaction>('transactions');
  runApp(const MyApp());
}

Dart

Create model class

import 'package:hive/hive.dart';
//create part
part 'Transaction.g.dart';

@HiveType(typeId: 0)
class Transaction extends HiveObject{
  @HiveField(0)
  late String name;
  @HiveField(1)
  late DateTime createDate;
  @HiveField(2)
  late bool isExpense=true;
  @HiveField(3)
  late double amount;
}
/*
    Some Changes need in this model
  1)Modify Model class
  2)Generate Model Adapter
  3)Register Model Adapter

*/

Dart

In Terminal type this command

flutter packages pub run build_runner build

Adding and retrieving data from the Hive box:

Now that we have a Hive box set up, we can start adding data to it. We’ll show you how to use Hive to add data to the box, retrieve it by key, and retrieve all the data in the box.

Future addTransaction(String name, double amount, bool isExpense) async {
  //create hive transaction object
  final transaction = Transaction()
    ..name = name
    ..createDate = DateTime.now()
    ..amount = amount
    ..isExpense = isExpense;

  final box = Boxes.getTransactions();
  box.add(transaction);
  //box.put('mykey', transaction);

  // final mybox = Boxes.getTransactions();
  // final myTransaction = mybox.get('key');
  // mybox.values;
  // mybox.keys;
}

Dart
@override
Widget build(BuildContext context) => Scaffold(
  appBar: AppBar(
    title: Text('Hive Expense Tracker'),
    centerTitle: true,
  ),
  //get value from hive using listerner
  body: ValueListenableBuilder<Box<Transaction>>(
    valueListenable: Boxes.getTransactions().listenable(),
    builder: (context, box, _) {
      final transactions = box.values.toList().cast<Transaction>();

      return buildContent(transactions);
    },
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.add),
    onPressed: () => showDialog(
      context: context,
      builder: (context) => TransactionDialog(
        onClickedDone: addTransaction,
      ),
    ),
  ),
);

Dart

Updating and deleting data in the Hive box:

Finally, we’ll cover how to perform CRUD operations on the data in the Hive box. We’ll show you how to update and delete data, and how to handle errors and exceptions that may occur during these operations.

void editTransaction(
    Transaction transaction,
    String name,
    double amount,
    bool isExpense,
    ) {
  transaction.name = name;
  transaction.amount = amount;
  transaction.isExpense = isExpense;

  // final box = Boxes.getTransactions();
  // box.put(transaction.key, transaction);

  transaction.save();
}

void deleteTransaction(Transaction transaction) {
  // final box = Boxes.getTransactions();
  // box.delete(transaction.key);

  transaction.delete();
  //setState(() => transactions.remove(transaction));
}



Dart

full Code:

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_hive_database/model/Transaction.dart';
import 'package:flutter_hive_database/page/transactions_page.dart';

//import thos packages
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main()async {
  WidgetsFlutterBinding.ensureInitialized();
  //init hive
  await Hive.initFlutter();
  Hive.registerAdapter(TransactionAdapter());
  //name of box
  await Hive.openBox<Transaction>('transactions');
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TransactionPage()
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

          ],
        ),
      ),
      );
  }
}
Dart

boxes.dart

import 'package:hive/hive.dart';

import 'model/Transaction.dart';

class Boxes {
  static Box<Transaction> getTransactions() =>
      Hive.box<Transaction>('transactions');
}

Dart

Transaction.dart as Model class

import 'package:hive/hive.dart';
//create part
part 'Transaction.g.dart';

@HiveType(typeId: 0)
class Transaction extends HiveObject{
  @HiveField(0)
  late String name;
  @HiveField(1)
  late DateTime createDate;
  @HiveField(2)
  late bool isExpense=true;
  @HiveField(3)
  late double amount;
}
/*
    Some Changes need in this model
  1)Modify Model class
  2)Generate Model Adapter
  3)Register Model Adapter

*/


Dart

transaction_dialog.dart

import 'package:flutter/material.dart';

import '../model/Transaction.dart';


class TransactionDialog extends StatefulWidget {
  final Transaction? transaction;
  final Function(String name, double amount, bool isExpense) onClickedDone;

  const TransactionDialog({
    Key? key,
    this.transaction,
    required this.onClickedDone,
  }) : super(key: key);

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

class _TransactionDialogState extends State<TransactionDialog> {
  final formKey = GlobalKey<FormState>();
  final nameController = TextEditingController();
  final amountController = TextEditingController();

  bool isExpense = true;

  @override
  void initState() {
    super.initState();

    if (widget.transaction != null) {
      final transaction = widget.transaction!;

      nameController.text = transaction.name;
      amountController.text = transaction.amount.toString();
      isExpense = transaction.isExpense;
    }
  }

  @override
  void dispose() {
    nameController.dispose();
    amountController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final isEditing = widget.transaction != null;
    final title = isEditing ? 'Edit Transaction' : 'Add Transaction';

    return AlertDialog(
      title: Text(title),
      content: Form(
        key: formKey,
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              SizedBox(height: 8),
              buildName(),
              SizedBox(height: 8),
              buildAmount(),
              SizedBox(height: 8),
              buildRadioButtons(),
            ],
          ),
        ),
      ),
      actions: <Widget>[
        buildCancelButton(context),
        buildAddButton(context, isEditing: isEditing),
      ],
    );
  }

  Widget buildName() => TextFormField(
    controller: nameController,
    decoration: InputDecoration(
      border: OutlineInputBorder(),
      hintText: 'Enter Name',
    ),
    validator: (name) =>
    name != null && name.isEmpty ? 'Enter a name' : null,
  );

  Widget buildAmount() => TextFormField(
    decoration: InputDecoration(
      border: OutlineInputBorder(),
      hintText: 'Enter Amount',
    ),
    keyboardType: TextInputType.number,
    validator: (amount) => amount != null && double.tryParse(amount) == null
        ? 'Enter a valid number'
        : null,
    controller: amountController,
  );

  Widget buildRadioButtons() => Column(
    children: [
      RadioListTile<bool>(
        title: Text('Expense'),
        value: true,
        groupValue: isExpense,
        onChanged: (value) => setState(() => isExpense = value!),
      ),
      RadioListTile<bool>(
        title: Text('Income'),
        value: false,
        groupValue: isExpense,
        onChanged: (value) => setState(() => isExpense = value!),
      ),
    ],
  );

  Widget buildCancelButton(BuildContext context) => TextButton(
    child: Text('Cancel'),
    onPressed: () => Navigator.of(context).pop(),
  );

  Widget buildAddButton(BuildContext context, {required bool isEditing}) {
    final text = isEditing ? 'Save' : 'Add';

    return TextButton(
      child: Text(text),
      onPressed: () async {
        final isValid = formKey.currentState!.validate();
        if (isValid) {
          final name = nameController.text;
          final amount = double.tryParse(amountController.text) ?? 0;

          widget.onClickedDone(name, amount, isExpense);

          Navigator.of(context).pop();
        }
      },
    );
  }
}

Dart

Transaction_page.dart

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

import '../boxes.dart';
import '../model/Transaction.dart';
import '../widget/transaction_dialog.dart';

class TransactionPage extends StatefulWidget {
  @override
  _TransactionPageState createState() => _TransactionPageState();
}

class _TransactionPageState extends State<TransactionPage> {
  @override
  void dispose() {
    //close hive
    Hive.close();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: Text('Hive Expense Tracker'),
      centerTitle: true,
    ),
    //get value from hive using listerner
    body: ValueListenableBuilder<Box<Transaction>>(
      valueListenable: Boxes.getTransactions().listenable(),
      builder: (context, box, _) {
        final transactions = box.values.toList().cast<Transaction>();

        return buildContent(transactions);
      },
    ),
    floatingActionButton: FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () => showDialog(
        context: context,
        builder: (context) => TransactionDialog(
          onClickedDone: addTransaction,
        ),
      ),
    ),
  );

  Widget buildContent(List<Transaction> transactions) {
    if (transactions.isEmpty) {
      return Center(
        child: Text(
          'No expenses yet!',
          style: TextStyle(fontSize: 24),
        ),
      );
    } else {
      final netExpense = transactions.fold<double>(
        0,
            (previousValue, transaction) => transaction.isExpense
            ? previousValue - transaction.amount
            : previousValue + transaction.amount,
      );
      final newExpenseString = '\$${netExpense.toStringAsFixed(2)}';
      final color = netExpense > 0 ? Colors.green : Colors.red;

      return Column(
        children: [
          SizedBox(height: 24),
          Text(
            'Net Expense: $newExpenseString',
            style: TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 20,
              color: color,
            ),
          ),
          SizedBox(height: 24),
          Expanded(
            child: ListView.builder(
              padding: EdgeInsets.all(8),
              itemCount: transactions.length,
              itemBuilder: (BuildContext context, int index) {
                final transaction = transactions[index];

                return buildTransaction(context, transaction);
              },
            ),
          ),
        ],
      );
    }
  }

  Widget buildTransaction(
      BuildContext context,
      Transaction transaction,
      ) {
    final color = transaction.isExpense ? Colors.red : Colors.green;
    final date = DateFormat.yMMMd().format(transaction.createDate);
    final amount = '\$' + transaction.amount.toStringAsFixed(2);

    return Card(
      color: Colors.white,
      child: ExpansionTile(
        tilePadding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
        title: Text(
          transaction.name,
          maxLines: 2,
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
        ),
        subtitle: Text(date),
        trailing: Text(
          amount,
          style: TextStyle(
              color: color, fontWeight: FontWeight.bold, fontSize: 16),
        ),
        children: [
          buildButtons(context, transaction),
        ],
      ),
    );
  }

  Widget buildButtons(BuildContext context, Transaction transaction) => Row(
    children: [
      Expanded(
        child: TextButton.icon(
          label: Text('Edit'),
          icon: Icon(Icons.edit),
          onPressed: () => Navigator.of(context).push(
            MaterialPageRoute(
              builder: (context) => TransactionDialog(
                transaction: transaction,
                onClickedDone: (name, amount, isExpense) =>
                    editTransaction(transaction, name, amount, isExpense),
              ),
            ),
          ),
        ),
      ),
      Expanded(
        child: TextButton.icon(
          label: Text('Delete'),
          icon: Icon(Icons.delete),
          onPressed: () => deleteTransaction(transaction),
        ),
      )
    ],
  );

  //lets check  and add data
  Future addTransaction(String name, double amount, bool isExpense) async {
    //create hive transaction object
    final transaction = Transaction()
      ..name = name
      ..createDate = DateTime.now()
      ..amount = amount
      ..isExpense = isExpense;

    final box = Boxes.getTransactions();
    box.add(transaction);
    //box.put('mykey', transaction);

    // final mybox = Boxes.getTransactions();
    // final myTransaction = mybox.get('key');
    // mybox.values;
    // mybox.keys;
  }



  void editTransaction(
      Transaction transaction,
      String name,
      double amount,
      bool isExpense,
      ) {
    transaction.name = name;
    transaction.amount = amount;
    transaction.isExpense = isExpense;

    // final box = Boxes.getTransactions();
    // box.put(transaction.key, transaction);

    transaction.save();
  }

  void deleteTransaction(Transaction transaction) {
    // final box = Boxes.getTransactions();
    // box.delete(transaction.key);

    transaction.delete();
    //setState(() => transactions.remove(transaction));
  }



}

Dart

Conclusion and further resources:

In this section, we’ll summarize what we’ve covered in this tutorial and suggest some further resources for learning more about Hive and NoSQL databases in general. We’ll also encourage viewers to try implementing Hive in their own Flutter projects and share their results with us.

By the end of this tutorial, you’ll have a solid understanding of how to use Hive in your Flutter app to store and manage data. You’ll be able to create a Hive box, define adapters for your data models, add and retrieve data from the box, and perform CRUD operations on your data. Whether you’re building an Android, iOS, or web app with Flutter, Hive can be a valuable tool for managing your data.