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 CRUD: Create, 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.0dev_dependencies: hive_generator: ^1.1.1 build_runner: ^2.1.7
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, List
, Map
, DateTime
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, ) ); } }

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 typeId
s between 0 and 223 are allowed. Make sure to use typeId
s consistently. Your changes have to be compatible with previous versions of the box. It’s recommended to register all TypeAdapter
s 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 TypeAdapter
s for almost any class.
- To generate a
TypeAdapter
for a class, annotate it with@HiveType
and provide atypeId
(between 0 and 223) - Annotate all fields which should be stored with
@HiveField
- You can Specify part ‘hivemodel.g.dart’ in hivemodel.dart .
- 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:
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,))); }, )IconButton( icon: Icon(Icons.edit,color: Colors.blue,), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (_) => UpdateData( isEdit: true,position: position, getServicemodel:getdeatils,))); }),
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 withbox.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 withbox.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:
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.