Exploring Hive Flutter CRUD Operations

Exploring Hive Flutter CRUD Operations

Quick guide to understanding Hive using Flutter

In this tutorial, we'll create a Flutter application that utilizes Hive for local data persistence. The application will demonstrate basic CRUD (Create, Read, Update, Delete) operations on a simple "Item" model. Follow the step-by-step guide below to set up the project and implement the functionality.

Step 1: Project Setup

  1. Create a new Flutter project by running the following command:

     flutter create flutter_hive_crud
    
  2. Open the pubspec.yaml file and add the necessary dependencies:

     dependencies:
       hive: ^2.2.3
       fluttertoast: ^8.2.4
       equatable: ^2.0.5
       hive_flutter: ^1.1.0
    
  3. Run flutter pub get to install the dependencies.

Step 2: Folder Structure

Create the following folder structure in the lib directory:

  • lib/main.dart

  • lib/screens/main_screen.dart

  • lib/screens/widgets/are_you_sure.dart

  • lib/screens/widgets/single_list_tile.dart

  • lib/screens/widgets/text_action.dart

  • lib/screens/widgets/toast.dart

  • lib/model/item.dart

  • lib/controller/controller.dart

  • lib/constants/string_constants.dart

  • lib/constants/enums/status.dart

  • lib/constants/enums/yes_no.dart

Step 3: Code Implementation

Let's walk through the code step by step, explaining each section as we paste it into the respective files.


1. main.dart

Purpose: This is the entry point of our Flutter application. Here, we initialize Hive, open the Hive box, and run the main application.

Paste the following code into lib/main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_hive_crud/screens/main_screen.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'constants/string_constants.dart';

Future<void> main() async {
  // Ensure Flutter is initialized
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Hive and open the Hive box
  await Hive.initFlutter();
  await Hive.openBox(StringConstants.hiveBox);

  // Run the main application
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // MaterialApp sets up the basic structure of the app
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
        useMaterial3: true,
      ),
      home: const MainScreen(), // Our main screen is set as the home screen
    );
  }
}

Explanation:

  • We use the main function as the entry point for our application.

  • WidgetsFlutterBinding.ensureInitialized(); ensures that Flutter is initialized.

  • Hive.initFlutter(); initializes Hive for Flutter.

  • Hive.openBox(StringConstants.hiveBox); opens the Hive box using the defined box name.

  • runApp(const MyApp()); starts our application by running the MyApp widget.


2. main_screen.dart

Purpose: The main screen of our application. It displays a list of items and provides functionality for CRUD operations.

Paste the following code into lib/screens/main_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_hive_crud/screens/widgets/are_you_sure.dart';
import 'package:flutter_hive_crud/screens/widgets/single_list_tile.dart';
import 'package:hive/hive.dart';
import '../constants/string_constants.dart';
import '../controller/controller.dart';
import '../model/item.dart';

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController title = TextEditingController();
  final TextEditingController quantity = TextEditingController();
  late HiveController hiveController;
  final hiveBox = Hive.box(StringConstants.hiveBox);
  List<Map<String, dynamic>> items = [];
  int editKey = 0;
  bool isCreate = true;

  @override
  void initState() {
    super.initState();
    hiveController = HiveController(
      context: context,
      fetchDataFunction: fetchData,
    );
    fetchData();
  }

  // fetch data
  void fetchData() {
    setState(() {
      items = hiveController.fetchData();
    });
  }

  // delete dialog
  void deleteDialog({required int key}) {
    areYouSureDialog(
      title: 'Delete item',
      content: 'Are you sure you want to delete item',
      key: key,
      isKeyInvolved: true,
      context: context,
      action: hiveController.deleteItem,
    );
  }

  // edit handle
  void editHandle({required Item item, required int key}) {
    Item item = Item.fromMap(hiveBox.get(key));
    print(item.title);

    setState(() {
      title.text = item.title;
      quantity.text = item.quantity.toString();
      editKey = key;
      isCreate = false;
    });

    // modal for editing
    itemModal();
  }

  // clear all dialog
  void clearAllDialog() {
    areYouSureDialog(
      title: 'Clear items',
      content: 'Are you sure you want to clear items',
      context: context,
      action: hiveController.clearItems,
    );
  }

  // submit item
  void submitItem() {
    FocusScope.of(context).unfocus();
    var valid = _formKey.currentState!.validate();

    if (!valid) {
      return;
    }
    Item item = Item(
      title: title.text,
      quantity: int.parse(quantity.text),
    );
    if (isCreate) {
      hiveController.createItem(item: item);
      title.clear();
      quantity.clear();
    } else {
      hiveController.editItem(item: item, itemKey: editKey);

      // resetting data
      setState(() {
        title.clear();
        quantity.clear();
        editKey = 0;
        isCreate = true;
      });
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    fetchData();
  }

  // modal for new/edit item
  Future itemModal() {
    return showModalBottomSheet(
      context: context,
      builder: (context) => Padding(
        padding: const EdgeInsets.all(8.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextFormField(
                autofocus: true,
                controller: title,
                textInputAction: TextInputAction.next,
                decoration: const InputDecoration(
                  hintText: 'Enter Title',
                  label: Text('Title'),
                ),
                validator: (value) {
                  if (value!.isEmpty || value.length < 3) {
                    return 'Title needs to be valid';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 10),
              TextFormField(
                controller: quantity,
                textInputAction: TextInputAction.done,
                keyboardType: TextInputType.number,
                decoration: const InputDecoration(
                  hintText: 'Enter Quantity',
                  label: Text('Quantity'),
                ),
                validator: (value) {
                  if (value!.isEmpty) {
                    return 'Quantity can not be empty';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 10),
              ElevatedButton.icon(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.brown.withOpacity(0.5),
                ),
                onPressed: () => submitItem(),
                icon: const Icon(
                  Icons.check,
                  color: Colors.white,
                ),
                label: const Text(
                  'Submit',
                  style: TextStyle(color: Colors.white),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () => itemModal(),
        child: const Icon(Icons.add),
      ),
      appBar: AppBar(
        title: const Text('Hive Flutter'),
        actions: [
          items.isNotEmpty
              ? IconButton(
                  onPressed: () => clearAllDialog(),
                  icon: const Icon(Icons.delete_forever),
                )
              : const SizedBox.shrink()
        ],
      ),
      body: items.isNotEmpty
          ? ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, index) {
                Item item = Item.fromMap(items[index]);
                return SingleListItem(
                  index: index,
                  item: item,
                  items: items,
                  deleteItem: hiveController.deleteItem,
                  editHandle: editHandle,
                  itemKey: items[index]['key'],
                  deleteDialog: deleteDialog,
                );
              })
          : const Center(
              child: Wrap(
                crossAxisAlignment: WrapCrossAlignment.center,
                children: [
                  Icon(Icons.add),
                  SizedBox(width: 10),
                  Text(
                    'Items are empty',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
    );
  }
}

Explanation:

  • MainScreen is a StatefulWidget responsible for rendering the main screen of our app.

  • It imports necessary dependencies and initializes instances of HiveController and HiveBox.

  • The state includes properties like editKey for tracking the key being edited, isCreate for determining if we are creating a new item, and a list of items fetched from Hive.


3. are_you_sure.dart

Purpose: This widget provides a dialog to confirm user actions, such as deleting an item.

Paste the following code into lib/screens/widgets/are_you_sure.dart:

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';


Future<void> areYouSureDialog({
  required String title,
  required String content,
  required BuildContext context,
  required Function action,
  bool isKeyInvolved = false,
  int key = 0,
}) {
  return showDialog(
    context: context,
    builder: (context) => Platform.isIOS
        ? 
// Cupertino Dialog Box
CupertinoAlertDialog(
      title: Text(
        title,
        style: const TextStyle(
          fontWeight: FontWeight.w700,
          color: Colors.black,
          fontSize: 16,
        ),
      ),
      content: Text(content),

      actions: [
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: ElevatedButton(
            style: ElevatedButton.styleFrom(
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10),
              ),
            ),
            onPressed: () => isKeyInvolved ? action(key: key) : action(),
            child: const Text('Yes'),
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: ElevatedButton(
            style: ElevatedButton.styleFrom(
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10),
              ),
            ),
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Dismiss'),
          ),
        ),
      ],
    )

// Material Dialog Box
        : AlertDialog(
      title: Text(
        title,
        style: const TextStyle(
          fontWeight: FontWeight.w700,
          color: Colors.black,
          fontSize: 16,
        ),
      ),
      content: Text(content),
      actions: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(horizontal: 5),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
          ),
          onPressed: () => isKeyInvolved ? action(key: key) : action(),
          child: const Text('Yes'),
        ),
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(horizontal: 5),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
          ),
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('Dismiss'),
        ),
      ],
    ),
  );
}

Explanation:

  • areYouSureDialog is a reusable function for showing a confirmation dialog.

  • It takes parameters such as title, content, context, and action to customize the dialog's appearance and behavior.

  • The Platform.isIOS check helps determine whether to use a Cupertino or Material design dialog based on the platform.


4. single_list_tile.dart

Purpose: This widget represents a single item in the list, allowing users to interact with it.

Paste the following code into lib/screens/widgets/single_list_tile.dart:

import 'package:flutter/material.dart';
import 'package:flutter_hive_crud/screens/widgets/text_action.dart';

import '../../constants/enums/yes_no.dart';
import '../../model/item.dart';

class SingleListItem extends StatelessWidget {
  const SingleListItem({
    super.key,
    required this.index,
    required this.item,
    required this.items,
    required this.deleteItem,
    required this.editHandle,
    required this.deleteDialog,
    required this.itemKey,
  });

  final int index;
  final Item item;
  final List<Map<String, dynamic>> items;
  final Function deleteItem;
  final Function editHandle;
  final Function deleteDialog;
  final int itemKey;

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      key: ValueKey(index),
      confirmDismiss: (direction) => showDialog(
        context: context,
        builder: (BuildContext context) => AlertDialog(
          elevation: 3,
          title: const Text(
            'Are you sure?',
            style: TextStyle(
              fontWeight: FontWeight.w600,
              color: Colors.black,
              fontSize: 18,
            ),
          ),
          content: Text(
            'Do you want to remove ${item.title} from list?',
            style: const TextStyle(
              color: Colors.black,
              fontSize: 14,
            ),
          ),
          actions: [
            textAction('Yes', YesNo.yes, context),
            textAction('No', YesNo.no, context),
          ],
        ),
      ),
      onDismissed: (direction) => deleteItem(key: itemKey),
      direction: DismissDirection.endToStart,
      background: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(10),
          color: Colors.red,
        ),
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        child: const Icon(
          Icons.delete,
          color: Colors.white,
          size: 40,
        ),
      ),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Card(
          child: ListTile(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10),
            ),
            contentPadding: const EdgeInsets.only(left: 5),
            tileColor: Colors.brown.withOpacity(0.7),
            leading: CircleAvatar(
              backgroundColor: Colors.brown.shade500,
              child: const Icon(
                Icons.circle_outlined,
                color: Colors.white,
              ),
            ),
            title: Text(
              item.title,
              style: const TextStyle(
                color: Colors.white,
              ),
            ),
            subtitle: Text(
              item.quantity.toString(),
              style: const TextStyle(
                color: Colors.white,
              ),
            ),
            trailing: Wrap(
              children: [
                IconButton(
                  onPressed: () =>
                      editHandle(item: item, key:itemKey),
                  icon: const Icon(
                    Icons.edit,
                    color: Colors.white,
                  ),
                ),
                IconButton(
                  onPressed: () => deleteDialog(key: itemKey),
                  icon: const Icon(
                    Icons.delete,
                    color: Colors.white,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Explanation:

  • SingleListItem is a widget representing a single item in the list.

  • It uses the areYouSureDialog function for handling deletion confirmation.

  • Provides options for editing and deleting an item.


5. text_action.dart

Purpose: This widget represents an interactive text button for various actions.

Paste the following code into lib/screens/widgets/text_action.dart:

import 'package:flutter/material.dart';

import '../../constants/enums/yes_no.dart';

Widget textAction(String text, YesNo operation, BuildContext context) {
  return ElevatedButton(
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.brown.withOpacity(0.5),
    ),
    child: Text(
      text,
      style: const TextStyle(
        color: Colors.white,
      ),
    ),
    onPressed: () {
      switch (operation) {
        case YesNo.no:
          Navigator.of(context).pop(false);
          break;
        case YesNo.yes:
          Navigator.of(context).pop(true);
          break;
        default:
      }
    },
  );
}

Explanation:

  • textAction is a reusable widget for creating interactive text buttons.

  • It takes parameters such as text, operation, and context to customize the button's appearance and behavior.


6. toast.dart

Purpose: This utility provides informative toast messages based on the status.

Paste the following code into lib/screens/widgets/toast.dart:

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../../constants/enums/status.dart';

toastInfo({
  required String msg,
  required Status status,
}) {
  return Fluttertoast.showToast(
    msg: msg,
    backgroundColor: status == Status.error ? Colors.red : Colors.green,
    toastLength: Toast.LENGTH_LONG,
    gravity: ToastGravity.TOP,
    timeInSecForIosWeb: 2,
  );
}

Explanation:

  • toastInfo is a utility function for showing informative toast messages.

  • It takes parameters such as msg (message) and status to customize the toast appearance based on success or error.


7. item.dart

Purpose: This file defines the Item class representing an item in our application.

Paste the following code into lib/model/item.dart:

import 'package:equatable/equatable.dart';

class Item extends Equatable {
  final String title;
  final int quantity;

  const Item({
    required this.title,
    required this.quantity,
  });

  @override
  List<Object> get props => [ title, quantity];

  Item copyWith({
    String? title,
    int? quantity,
  }) {
    return Item(
      title: title ?? this.title,
      quantity: quantity ?? this.quantity,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      'title': title,
      'quantity': quantity,
    };
  }

  factory Item.fromMap(Map<String, dynamic> map) {
    return Item(
      title: map['title'] as String,
      quantity: map['quantity'] as int,
    );
  }
}

Explanation:

  • Item is a model class representing an item in our application.

  • It extends Equatable to enable value-based equality checks.


8. controller.dart

Purpose: This file defines the Item class representing an item in our application.

Paste the following code into lib/controller/controller.dart:

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_hive_crud/constants/string_constants.dart';
import 'package:flutter_hive_crud/screens/widgets/toast.dart';

import '../constants/enums/status.dart';
import '../model/item.dart';
import 'package:hive_flutter/hive_flutter.dart';

class HiveController {
  final BuildContext context;
  final Function fetchDataFunction;

  HiveController({required this.context, required this.fetchDataFunction});

  final hiveBox = Hive.box(StringConstants.hiveBox);

  // fetch data
  List<Map<String, dynamic>> fetchData() {
    final data = hiveBox.keys.map((key) {
      final item = hiveBox.get(key);
      return {
        'key': key,
        'id': item['id'],
        'title': item['title'],
        'quantity': item['quantity']
      };
    }).toList();

    return data.reversed.toList();
  }

  Future<void> createItem({
    required Item item,
  }) async {
    try {
      await hiveBox.add(item.toMap());
      afterAction(keyword: 'saved');
    } catch (e) {
      toastInfo(
        msg: 'An error occurred while trying to create item',
        status: Status.error,
      );
      if (kDebugMode) {
        print('Error occurred $e');
      }
    }
  }

  Future<void> editItem({required Item item, required int itemKey}) async {
    try {
      hiveBox.put(itemKey, item.toMap());
      afterAction(keyword: 'edited');
    } catch (e) {
      toastInfo(
        msg: 'An error occurred while trying to edit item',
        status: Status.error,
      );
      if (kDebugMode) {
        print('Error occurred $e');
      }
    }
  }

  Future<void> deleteItem({required int key}) async {
    try {
      await hiveBox.delete(key);
      afterAction(keyword: 'deleted');
    } catch (e) {
      toastInfo(
        msg: 'An error occurred while trying to delete item',
        status: Status.error,
      );
      if (kDebugMode) {
        print('Error occurred $e');
      }
    }
  }

  Future<void> clearItems() async {
    try {
      await hiveBox.clear();
      afterAction(keyword: 'deleted');
    } catch (e) {
      toastInfo(
        msg: 'An error occurred while trying to delete item',
        status: Status.error,
      );
      if (kDebugMode) {
        print('Error occurred $e');
      }
    }
  }

  void afterAction({required String keyword}) {
    toastInfo(
      msg: 'Successfully $keyword item',
      status: Status.success,
    );
    fetchDataFunction();
    Navigator.of(context).pop();
  }
}

Explanation:

  • HiveController is a class responsible for handling CRUD operations and managing the interaction with Hive.

  • It provides methods for creating, reading, updating, and deleting items.


9. string_constants.dart

Purpose: This file defines a class holding constant strings used across the application.

Paste the following code into lib/constants/string_constants.dart:

class StringConstants {
  static const hiveBox = 'items';
}

Explanation:

  • StringConstants is a utility class holding constant strings.

  • hiveBox is a constant string representing the name of the Hive box.


10. status.dart

Purpose: This file defines an enum representing different status types.

Paste the following code into lib/constants/enums/status.dart:

enum Status { error, success }

Explanation:

  • Status is an enumeration representing different status types.

  • It is used to determine the type of message to be displayed, such as an error or success message.


11. yes_no.dart

Purpose: This file defines an enum representing yes or no options.

Paste the following code into lib/constants/enums/yes_no.dart:

enum YesNo { yes, no }

Explanation:

  • YesNo is an enumeration representing yes or no options.

  • It is used in different parts of the application for decision-making.


Feel free to experiment with the code and run the application to see how each piece fits together. If you have any questions or need further clarification on any part, feel free to reach out!

To explore Hive more use: https://pub.dev/packages/hive

Did you find this article valuable?

Support Flutter Aware by becoming a sponsor. Any amount is appreciated!

ย