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
Create a new Flutter project by running the following command:
flutter create flutter_hive_crud
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
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 theMyApp
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 aStatefulWidget
responsible for rendering the main screen of our app.It imports necessary dependencies and initializes instances of
HiveController
andHiveBox
.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
, andaction
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
, andcontext
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) andstatus
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