Flutter Offline Storage Using HIVE Database

Flutter Offline Storage Using HIVE Database

In this article, describe about Storing data offline from the REST API responses. we will see, How to Store a List of data in Hive Database by using State Management of Redux and performing CRUD operations.

Why choose Hive?

Hive is an Easy, Fast, Secure NoSQL Database Implementation for Flutter Applications. Hive has more PUB Points and Like 👍 when compare to other databases (ObjectBox, Moor, SQLite, etc.,). Hive is a lightweight and fast key-value database solution and is written in pure Dart. Hive is a dart package used in Flutter applications for storing data locally and manipulating the data on a targeted device.

A database typically allows developers to store, access, search, update, query, and otherwise manipulate data in the database via a developer language or API. These types of operations are done within an application, in the background, typically hidden from end-users. Many applications need a database as part of their technology stack. The most typical database operations are CRUDCreate, Read, Update, Delete.

What are boxes?

Hive is pretty fantastic at storing the data securely using an AES-256 (robust encryption) method. So, where does it store the data? In the hive boxes!

All data stored in Hive is organized inboxes. For a small app, a single box might be enough. For more advanced problems, boxes are a great way to organize your data. Boxes can also be encrypted to store sensitive data.

Before you can use a box, you have to open it. For regular boxes, this loads all of its data from the local storage into memory for immediate access.

Hive uses the concept of “boxes” for storing data on the database. boxes are flexible and can only handle simple relationships between data. It stores data in boxes having key-value sets. So, to read this data stored inside the boxes, you will have to open the boxes first to either read or compose it.

Let’s Start our Implementation:

Before moving on to the CRUD operations of the database, First, we get data from API response and Stored in Hive boxes with State management of Redux.

We have already published an article on how to get data from API response and stored data in Redux. here is the link👉 Redux-State management with Redux in Flutter. Using this Knowledge you can build your code with Redux. Now we can Skip this Step and directly move to how to store data In the HIVE database from Redux.

When users open App as offline display data from the database. when the user has a network connection get data from the server, Update DB and display data (user request → server → get response → Redux (Reducer) → Update DB(Hive) → Display data ).

Add Dependencies:

Add the hive, hive_flutter, and path_provider packages to your pubspec.yaml file. we also need to add  hive_generator and build_runner to the dev dependencies.

dependencies:
  hive: ^2.0.4
  hive_flutter: ^1.1.0
  path_provider: ^2.0.8
  flutter_redux: ^0.8.2
  redux: ^5.0.0
  redux_thunk: ^0.4.0

First, we initialize Hive in Main Function before we perform any operations related hive database.

await Hive.initFlutter();

Initialize hive with the path of the directory .it’s Optional. We can find the location on HDFS(Hadoop Distributed File System) where the directories for the database are made by checking hive.

Directory directory = await getApplicationDocumentsDirectory();
Hive.init(directory.path);

Hive supports all primitive types, ListMapDateTime and Uint8List. If you want to store other objects, you have to register a TypeAdapter which converts the object from and to binary form. When you want Hive to use a TypeAdapter, you have to register it.

Hive.registerAdapter(ServiceAdapter());

main.dart

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Directory directory = await getApplicationDocumentsDirectory();
Hive.init(directory.path);
Hive.registerAdapter(ServiceAdapter());
runApp(MyApp());
}

final navigatorKey = GlobalKey<NavigatorState>();

class MyApp extends StatefulWidget{
const MyApp({Key? key}) : super(key: key);
_MyAppState createState()=>_MyAppState();
}

class _MyAppState extends State<MyApp> {
final store = Store<AppState>(
appReducer,
initialState: AppState.initial(),
middleware: [thunkMiddleware, apiMiddleware, ],
);

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

@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);

return StoreProvider(
store: this.store,
child: MaterialApp(
builder: (context, child) {
return MediaQuery(data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), child:child!);
},
navigatorKey: navigatorKey,
initialRoute: '/feed',
onGenerateRoute: RouteGenerator.generateRoute,
debugShowCheckedModeBanner: false,
)
);
}
}
Get List of data from Hive
Get a List of data from Hive

hivemodel.dart:

Create a model class for the hive. Two things are needed for that: An instance of the adapter and a typeId. Every type has a unique typeId which is used to find the correct adapter when a value is brought back from the disk. All typeIds between 0 and 223 are allowed. Make sure to use typeIds consistently. Your changes have to be compatible with previous versions of the box. It’s recommended to register all TypeAdapters before opening any boxes.

import 'package:hive/hive.dart';
part 'hivemodel.g.dart';

@HiveType(typeId: 1,adapterName: "ServiceAdapter")
class GetService{
@HiveField(0)
String name;

@HiveField(1)
String Lastname;

@HiveField(2)
String status;

GetService({required this.name,required this.status,required this.lastname});
}

hivemodel.g.dart

The hive_generator package can automatically generate TypeAdapters for almost any class.

  1. To generate a TypeAdapter for a class, annotate it with @HiveType and provide a typeId (between 0 and 223)
  2. Annotate all fields which should be stored with @HiveField
  3. You can Specify part ‘hivemodel.g.dart’ in hivemodel.dart .
  4. Open terminal and Run flutter packages pub run build_runner build then automatically create hivemodel.g.dart .
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'hivemodel.dart';

// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************

class ServiceAdapter extends TypeAdapter<GetService> {
@override
final int typeId = 1;

@override
GetService read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return GetService(
name: fields[0] as String,
status: fields[2] as String,
lastname: fields[1] as String,
);
}

@override
void write(BinaryWriter writer, GetService obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.lastname)
..writeByte(2)
..write(obj.status);
}

@override
int get hashCode => typeId.hashCode;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ServiceAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

feedscreen.dart:

await Hive.openBox<GetService>(‘service’);

when we start to store data in the hive. we need to open Hive Box. here ‘service’ is the name of the box. It is case-insensitive.

class _FeedScreenState extends State<FeedScreen> {
int listLenght = 0;

@override
void initState(){
hiveopen();
}

hiveopen() async {
box = await Hive.openBox<GetService>('service');

setState(() {
ListServiceItems = box.values.toList();
});
}

putalldata(RequestProps props) async {
box = await Hive.openBox<GetService>('service'); //open box
box.clear(); //clear all values in Hive
for(int i=0; i<listLenght; i++) {
GetService adddata = new GetService(
name: props.feedsuccess!.success![i].waiterId!.firstName.toString(),
status: props.feedsuccess!.success![i].status.toString(),
lastname: props.feedsuccess!.success![i].waiterId!.lastName.toString());
box.add(adddata); // add data from hive
}
setState(() {
ListServiceItems = box.values.toList(); //get values from Hive
});
}

var data = {
"userId":userid.toString()
};

void handleInitialBuild(RequestProps props) {
props.feedsuccessapi("");
}

@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RequestProps>(
converter: (store) => mapStateToProps(store),
onInitialBuild: (props) => this.handleInitialBuild(props),
builder: (context, props) {
Widget ? body;

if (props.loading == true) {
body = Center(
child: CircularProgressIndicator(),
);
}

else if(props.feedsuccess != null) {
listLenght = props.feedsuccess!.success!.length;
Timer(Duration(milliseconds: 100), () {
putalldata(props);
});
}

else if(props.error != null){
print("error");
}

return Scaffold(
appBar: AppBar(
title: Text('Home page'),
),

body:
ListView.builder(
itemCount: ListServiceItems.length,
itemBuilder: (context, position) {
GetService getdeatils = ListServiceItems[position];
var name = getdeatils.name;
var status = getdeatils.status;
var lastname = getdeatils.lastname;
return Card(
child: Container(
padding: EdgeInsets.all(7),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text( "USER NAME : "+name + " "+ lastname, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),
SizedBox(height: 10,),
Text("STATUS MESSAGE : "+ status),
],
)
)
);
})

);
});
}
}

Crud Operations:

Create Read Update Delete

Add the Icon button in feedscreen.dart . When the user click Add button and edit button open the AddandUpdate screen and pass the required parameter values.

IconButton(
  icon: Icon(Icons.add),
  onPressed: () {
    Navigator.push(context, MaterialPageRoute(builder: (_) => UpdateData( getServicemodel: null, position: -1, isEdit:false,)));
  },
)

ADD/Update Screen:

Here, we open the box and then use the add() method to append the data to our box. you can also use the put() method, the difference is with put() you have to specify the key for each value – box.put('key', item), but add() just gives each value an index and auto increments it.

Read data from Hive:

  • box.get('key) – get the value from a key.
  • box.getAt(index) – get the value from an index created with box.add().
  • box.values – this returns an iterable containing all the items in the box.
Updatedate.dart:

class UpdateData extends StatefulWidget {
bool isEdit;
int position = -1;
GetService? getServicemodel = null;
UpdateData( {Key? key, required this.isEdit,required this.position, required this.getServicemodel}) : super(key: key);

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

class _UpdateDataState extends State<UpdateData> {
var firstNameController = TextEditingController();
var lastNameController = TextEditingController();
var statusController = TextEditingController();

@override
Widget build(BuildContext context) {
if (widget.isEdit) {
firstNameController.text = widget.getServicemodel!.name;
lastNameController.text = widget.getServicemodel!.lastname;
statusController.text = widget.getServicemodel!.status;
}

return Scaffold(
appBar: AppBar(
title: Text('ADD and Update')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
SizedBox(height: 30),
Row(
children: [
Text("FIRSTNAME :", style: TextStyle(fontSize: 18, color: Colors.black)),
SizedBox(width: 20),
Expanded(
child:
TextField(controller: firstNameController,
textInputAction: TextInputAction.next,
)),
],
),
Row(
children: [
Text("LASTNAME :", style: TextStyle(fontSize: 18,color: Colors.black)),
SizedBox(width: 20),
Expanded(
child:
TextField(controller: lastNameController,
textInputAction: TextInputAction.next,
)),
],
),
Row(
children: [
Text("STATUS :",style: TextStyle(fontSize: 18,color: Colors.black)),
SizedBox(width: 40),
Expanded(
child:
TextField(controller: statusController,
textInputAction: TextInputAction.next,
)),
],
),
SizedBox(height: 20,),
MaterialButton(
color: Colors.blue,
child: Text("SAVE",
style: TextStyle(color: Colors.white, fontSize: 18, )),
onPressed: () async {
var firstName = firstNameController.text;
var lastName = lastNameController.text;
var status = statusController.text;
if (firstName.isNotEmpty &
lastName.isNotEmpty &
status.isNotEmpty) {
GetService updatedata = new GetService(
name: firstName,
lastname: lastName,
status: status);

if (widget.isEdit) {
var box = await Hive.openBox<GetService>('service');
box.putAt(widget.position, updatedata);
} else {
var box = await Hive.openBox<GetService>('service');
box.add(updatedata);
}
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => FeedScreen()),
(r) => false);
}
},
)
],
),
),
),
);
}
}

Delete:

  • box.delete('key) – get the value from a key.
  • box.deleteAt(index) – get the value from an index created with box.add().
  • box.deleteAll(keys) – accepts an iterable of keys, and deletes all the keys given.
IconButton(
icon: Icon(Icons.delete,color: Colors.red,),
onPressed: (){
final box = Hive.box<GetService>('service');
box.deleteAt(position);
setState(() => {
ListServiceItems.removeAt(position)
});
})

Demo video for online and offline mode:

Offline Mode and Online Mode - Flutter Developer in Australia


Written By:
Rajeswari S

working as a Mobile Developer in Flutter at Optisol Business Solutions. Having good knowledge about Flutter Dart language. Experience with integrating and using analytics tools like Firebase.

Key Achievements:
  • Received appreciations from leads.​
  • Publish blog about flutter redux.​
Free Consulting