Storing data locally is a task which has to be done by almost every app. Maybe, you want to cache responses from a REST API or you're building an offline-only app. In any case, choosing the right local database can make all the difference in how quickly you can develop the app and also in how performant the app will be.
Hive is a lightweight, yet powerful database which is easy to develop with and it also runs fast on the device. Unless you absolutely need to model your data with many relationships, in which case you should probably use SQLite, choosing this pure-Dart package with no native dependencies (it runs on Flutter Web!) can be the best option.
Project Setup
In this tutorial, you're going to learn Hive by building a simple "contacts" app which will store the name and age of a person. Doing this will allow us to cover all the core concepts of Hive. The starter project contains some basic UI and also a Contact
class having the two aforementioned fields.
Apart from the core hive package, there are also a bunch of supporting ones such as hive_flutter and also hive_generator which is used for creating custom TypeAdapter
s. Let's add all of them.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
hive: ^1.0.0
hive_flutter: ^0.2.1
# For OS-specific directory paths
path_provider: ^1.3.0
dev_dependencies:
hive_generator: ^0.5.1
build_runner:
Diving into Hive
Hive is centered around the idea of boxes, and no, they don't contain bees ??. A Box
has to be opened before use. In addition to the plain-flavored Box
es, there are also options which support lazy-loading of values and encryption. Basically, Hive is a persistent Map
on steroids.
Initialization
Before performing any of the CRUD operations, Hive needs to be initialized to, among other things, know in which directory it stores the data. It's best to initialize Hive right in the main
method.
main.dart
void main() async {
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
runApp(MyApp());
}
Boxes
Data can be stored and read only from an opened Box
. Opening a Box
loads all of its data from the local storage into memory for immediate access.
final contactsBox = await Hive.openBox('contacts');
To get an already opened instance, you can call Hive.box('name')
instead. It doesn't matter though if you try to call openBox
multiple times. Hive is smart, and it will return an already opened box with the given name, if you've previously called that method.
Box
and you'd rather not load it all into memory for even faster access, use a LazyBox
. It's still fast, but reading values from it happens right from the uncached local storage.To keep the code clean, it's probably a wise idea to open the Box
from only a single place and then to get it using Hive.box('name')
. Also, to prevent holding unnecessary data in memory, you can close the Box
when you're not going to need it anymore. Hive also has a handy method to close all boxes. It's a good practice to do this before the app exits, although as per the official documentation, it's not really necessary to do so.
Hive.close()
to close all open boxes. Don’t worry if the app is killed before you close Hive, it doesn’t matter.main.dart
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hive Tutorial',
home: FutureBuilder(
future: Hive.openBox('contacts'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return ContactPage();
}
// Although opening a Box takes a very short time,
// we still need to return something before the Future completes.
else
return Scaffold();
},
),
);
}
@override
void dispose() {
Hive.close();
super.dispose();
}
}
Storing Data
With the Box
opened, let's add a new contact to the database after we submit the form. There are two basic options of adding data - either call put(key, value)
and specify the key yourself, or call add
and utilize Hive's auto-incrementing keys. Unless you absolutely need to define the keys manually, calling add
is the better and simpler option.
String
or int
.The problem is that Hive supports only primitive types like int
or String
, plus additional standard types, which are List
, Map
and DateTime
. Of course, we have our own custom Contact
model class which we'd like to utilize.
contact.dart
class Contact {
final String name;
final int age;
Contact(this.name, this.age);
}
Trying to call the following would result in an exception.
contactsBox.add(Contact('John Doe', 20));
Out of the box (?), Hive doesn't know how to store objects of type Contact
. Sure, we could just convert the objects to JSON strings and call it a day, but there is a better, more native solution, and that is adding a TypeAdapter
.
Creating a TypeAdapter
Behind the scenes, Hive works with binary data. While it's entirely possible to write a custom adapter which fumbles with a BinaryWriter
and a BinaryReader
, it's much easier to let the hive_generator package do the hard job for you. Making an adapter for the Contact
class is then as simple as adding a few annotations.
contact.dart
import 'package:hive/hive.dart';
part 'contact.g.dart';
@HiveType()
class Contact {
@HiveField(0)
final String name;
@HiveField(1)
final int age;
Contact(this.name, this.age);
}
This, of course, requires running the Flutter developer's most favorite command:
TypeAdapter
. For example the number of a particular field should not be changed. Learn more from the official docs.Just generating a TypeAdapter
is not enough though. We also have to register it. There are two options in how this can be done.
- Register the
TypeAdapter
just for a singleBox
. - Register it for all the
Box
es.
In the case of our Contact App, we have only one Box
either way, so we're going to register the TypeAdapter
globally.
main.dart
void main() async {
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
Hive.registerAdapter(ContactAdapter(), 0);
runApp(MyApp());
}
Adding a Contact
Putting this all together in the Contact App we're building, we can now add contacts inputted from the form to the database.
new_contact_form.dart
...
void addContact(Contact contact) {
final contactsBox = Hive.box('contacts');
contactsBox.add(contact);
}
...
RaisedButton(
child: Text('Add New Contact'),
onPressed: () {
_formKey.currentState.save();
final newContact = Contact(_name, int.parse(_age));
addContact(newContact);
},
)
...
Reading Contacts
We want to display all the contacts inside a ListView
, so we somehow need to access all of the contacts present inside the Box
. Firstly, we'll need to specify the itemCount
for the ListView.builder
.
contact_page.dart
ListView _buildListView() {
final contactsBox = Hive.box('contacts');
return ListView.builder(
itemCount: contactsBox.length,
itemBuilder: (BuildContext context, int index) {
// Show contacts
},
);
}
Now comes the time to display the contacts on the screen. The simplest way to retrieve data is to call the contactsBox.get(someKey)
method. Since we're using auto-incrementing keys, we should be simply able to use the index
parameter.
contact_page.dart
itemBuilder: (BuildContext context, int index) {
final contact = contactsBox.get(index) as Contact;
return ListTile(
title: Text(contact.name),
subtitle: Text(contact.age.toString()),
);
},
It works, of course, only after you rebuild the widget after adding a new contact. We're going to fix that next. But first, although the get
method works with the data we currently have, is it always a safe bet to use it from things like ListView
builders? What if you call box.put()
instead of add()
and therefore specify the keys yourself. How would you display such "custom-keyed" entries in a ListView
?
Keys vs Indexes
In addition to accessing stored values by keys, you can also access them by an index. In this regard, Hive works very much like a regular List
. Every new value has practically an auto-incremented index. Of course, this means that by using auto-incrementing keys, the values of the two will be "in sync".
However, as soon as store a value by calling box.put('customKey', value)
, or when a value somewhere in the middle of the "list" is deleted, this implicit synchronization of keys and indexes will be gone. Therefore, in a ListView
and in other places where you don't really get values by their keys, you should call box.getAt()
instead of get()
, which takes in an index instead of a key.
final contact = contactsBox.getAt(index) as Contact;
Watching for Changes
Having to manually rebuild the UI every time a value changes inside a Box
is not the best developer experience. That's why there is the box.watch()
method which returns a Stream
of BoxEvent
s. This is plenty enough if you have a proper state management, for example with Bloc, where you don't expose Box
es directly to the UI. With a simple state management though, there's a better solution to watching the values than to plug this Stream
into a vanilla StreamBuilder
.
WatchBoxBuilder Widget
While the core hive package can run on just about any Dart platform, hive_flutter adds a WatchBoxBuilder
widget to simplify the UI development a bit by not having to use the StreamBuilder
together with all its boilerplate. Now, we can effortlessly update the UI whenever any change happens inside the contactsBox
.
contact_page.dart
Widget _buildListView() {
return WatchBoxBuilder(
box: Hive.box('contacts'),
builder: (context, contactsBox) {
return ListView.builder(
itemCount: contactsBox.length,
itemBuilder: (BuildContext context, int index) {
final contact = contactsBox.getAt(index) as Contact;
return ListTile(
title: Text(contact.name),
subtitle: Text(contact.age.toString()),
);
},
);
},
);
Updating & Deleting Contacts
Updating a value happens by overriding an old one either with the put(key)
or putAt(index)
methods. For deleting, there is, of course, delete
or deleteAt
.
We're going to perform these last two of CRUD operations from two IconButtons
to keep the code simple. All of the updates and deletes will be automatically reflected in the UI because of the WatchBoxBuilder
widget.
contact_page.dart
return ListTile(
title: Text(contact.name),
subtitle: Text(contact.age.toString()),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
return contactsBox.putAt(
index,
Contact('${contact.name}*', contact.age + 1),
);
},
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => contactsBox.deleteAt(index),
),
],
),
);
Box Compaction
According to the official documentation:
Since we are both updating and deleting values, sooner or later, the compaction will kick in. While you can leave the decision of when to compact completely up to Hive, invoking compaction manually is also possible, although rarely needed. We could, however, call compact()
right before closing all the Box
es, for example.
main.dart
@override
void dispose() {
Hive.box('contacts').compact();
Hive.close();
super.dispose();
}
Another option is to provide a custom compactionStrategy
while opening a Box
.
main.dart
future: Hive.openBox(
'contacts',
compactionStrategy: (int total, int deleted) {
return deleted > 20;
},
),
The default compaction strategy is reasonable enough though, so in most cases, you can just ignore what you learned in this last section altogether.
Conclusion
Hive is an easy-to-use, yet fast database with a support for custom TypeAdapters
. Being completely platform independent is also a huge plus. As of writing this, the author of this amazing package, Simon Leier, is working on adding the support for queries. Once that's implemented, Hive will be an even more powerful, fully-featured database. Subscribe below to grow your Flutter coding skills by getting important Flutter news sent right into your inbox on a weekly basis.
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
i got error on it.
import ‘package:path_provider/path_provider.dart’ as path_provider;
Did you add this package?
i did. i did as this tutorial done
the error looks like this
E/flutter (27356): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
E/flutter (27356): If you’re running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first.
E/flutter (27356): If you’re running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test’s `main()` method to initialize the binding.
E/flutter (27356): #0 defaultBinaryMessenger.
package:flutter/…/services/binary_messenger.dart:73
E/flutter (27356): #1 defaultBinaryMessenger
package:flutter/…/services/binary_messenger.dart:86
E/flutter (27356): #2 MethodChannel.binaryMessenger
package:flutter/…/services/platform_channel.dart:140
Did you stop and start the app completely?
I Made new project,
change this function :
void main() async{
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
runApp(MyApp());
}
add dependencies:
dependencies:
flutter:
sdk: flutter
path_provider: ^1.3.0
hive: ^1.0.0
hive_flutter: ^0.2.1
then run the project for the first time. it still got this error. the screen of emulator still white and stuck in white screen without any widget loaded.
will you check this ?
Launching libmain.dart on Android SDK built for x86 in debug mode…
Built buildappoutputsapkdebugapp-debug.apk.
I/flutter (32192): Overflow on channel: flutter/lifecycle. Messages on this channel are being discarded in FIFO fashion. The engine may not be running or you need to adjust the buffer size if of the channel.
I/Choreographer(32192): Skipped 84 frames! The application may be doing too much work on its main thread.
D/EGL_emulation(32192): eglMakeCurrent: 0xe9f857e0: ver 2 0 (tinfo 0xd41fedb0)
I/OpenGLRenderer(32192): Davey! duration=1617ms; Flags=1, IntendedVsync=82000941874057, Vsync=82002341874001, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=82002345063660, AnimationStart=82002345203460, PerformTraversalsStart=82002348638560, DrawStart=82002371170960, SyncQueued=82002374936060, SyncStart=82002377670460, IssueDrawCommandsStart=82002380969460, SwapBuffers=82002466093860, FrameCompleted=82002562433860, DequeueBufferDuration=25011000, QueueBufferDuration=349000,
E/flutter (32192): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
E/flutter (32192): If you’re running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first.
E/flutter (32192): If you’re running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test’s `main()` method to initialize the binding.
E/flutter (32192): #0 defaultBinaryMessenger.
package:flutter/…/services/binary_messenger.dart:73
E/flutter (32192): #1 defaultBinaryMessenger
package:flutter/…/services/binary_messenger.dart:86
E/flutter (32192): #2 MethodChannel.binaryMessenger
package:flutter/…/services/platform_channel.dart:140
E/flutter (32192): #3 MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:314
E/flutter (32192):
E/flutter (32192): #4 getApplicationDocumentsDirectory
package:path_provider/path_provider.dart:84
E/flutter (32192):
E/flutter (32192): #5 main
package:hive_anan/main.dart:6
E/flutter (32192):
E/flutter (32192): #6 _runMainZoned.. (dart:ui/hooks.dart:239:25)
E/flutter (32192): #7 _rootRun (dart:async/zone.dart:1124:13)
E/flutter (32192): #8 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter (32192): #9 _runZoned (dart:async/zone.dart:1516:10)
E/flutter (32192): #10 runZoned (dart:async/zone.dart:1500:12)
E/flutter (32192): #11 _runMainZoned. (dart:ui/hooks.dart:231:5)
E/flutter (32192): #12 _startIsolate. (dart:isolate-patch/isolate_patch.dart:305:19)
E/flutter (32192): #13 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
E/flutter (32192):
I’m not sure why this is happening to you but I hope this will help: https://stackoverflow.com/a/57775690/6515736
WidgetsFlutterBinding.ensureInitialized();
this line of code must be the first line in the main function…
// app’s main function be like this…
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final appDocumentDirectory =
await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDirectory.path);
runApp(MyApp());
}
i got the problem for my error
void main() async{
WidgetsFlutterBinding.ensureInitialized();
var appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
runApp(MyApp());
}
add this WidgetsFlutterBinding.ensureInitialized();
before you get the path
Oh, I see you resolved the issue yourself. Anyway, thanks for learning on Reso Coder!
thanks Matej.
talking about local storage,
i hve watch all the moor tutorial but will you make the tutorial to clean the moor structure like how to separate the DAO, DB and other class
Moor recently got updated with a lot of new features, so I’m considering making a brand new series.
> Hive.registerAdapter(ContactAdapter(), 0);
I think there should be a type ID there instead of a zero.
Hello Matij, the code for the adapter isn’t generated. I run the build command from the terminal and executes successfully but no class is generated and the ( part ‘xxx.dart’; ) gives an error saying: Target of URI hasn’t been generated: ‘custom.g.dart’.
Try running the generator that will generate the file referenced by the URI.
I tried using 1.7.1 version of the build runner then used any but it doesn’t help. any idea?
Got the same Error, have you resolved it yet ?
Ok I think i’ve found the solution, here it is :
import ‘package:hive/hive.dart’;
part ‘hidden_object.g.dart’;
@HiveType(typeId: 0)
class HiddenObject {
@HiveField(0)
final String name;
@HiveField(1)
final String photo;
@HiveField(2)
final String description;
@HiveField(3)
final DateTime creationDate;
HiddenObject(this.name, this.photo, this.description, this.creationDate);
}
Don’t forget the typeId: 0 in the @HiveType annotation !!!
I was using vs code, after seven hours of researching it came to my mind to try opening the same project with android studio instead and I ran the flutter pub build there and it was generated. It’s an IDE issue eventually.
Hi Matt,
Can i save data object in hive ? for example : Category object class,
@HiveType()
class CategoryInspection {
@HiveField(0)
String idInspeksi;
@HiveField(1)
String title;
@HiveField(2)
String nama;
@HiveField(3)
String alamat;
@HiveField(4)
String telepon;
@HiveField(5)
String jenisMobil;
@HiveField(6)
Category category;
hello
how I can get a value with a condition like select * from mytable where my name = ‘jon’
thanks man its really help me…..
the most beautiful site and youtube channel
Hello Guys, Does Hive has a tool for manage database file like ( Browser DB for SQLITE ) ?
Because hive_generator >=0.6.0 depends on dartx ^0.2.0 and chatting depends on dartx ^0.4.0, hive_generator >=0.6.0 is forbidden.
So, because chatting depends on hive_generator ^0.7.0+2, version solving failed.
pub get failed (1; So, because chatting depends on hive_generator ^0.7.0+2, version solving failed.)
When I execute “pub get”, I got the above error.
The below is pubspec.yaml.
Could you help me how to fix it?
environment:
sdk: “>=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
agora_rtc_engine: ^1.0.11
animator: ^1.0.0+5 # old ok
cached_network_image: ^2.2.0+1
cloud_firestore: ^0.13.5
country_codes: ^0.1.0
cupertino_icons: ^0.1.3
dartx: ^0.4.0
#devicelocale: ^0.2.3
dio: ^3.0.9
firebase_analytics: ^5.0.11
firebase_auth: # use the latest version
firebase_core: ^0.4.4+3
firebase_messaging: ^6.0.13
firebase_storage: ^3.1.5
flt_telephony_info: ^0.1.3
flutter_facebook_login: ^3.0.0
flutter_image_compress: ^0.6.7
flutter_local_notifications: ^1.4.3
flutter_native_timezone: ^1.0.4
flutter_slidable: "^0.5.4"
geolocator: ^5.3.1
google_sign_in: ^4.4.4
hive: ^1.4.1+1
hive_flutter: ^0.3.0+2
http: ^0.12.1
intl: ^0.16.1
fluttertoast: ^4.0.1
image_cropper: ^1.2.1
image_picker: ^0.6.6+1
#moor: ^3.0.2
#moor_ffi: ^0.5.0
package_info: ^0.4.0+17
path_provider: ^1.6.7 # For OS-specific directory paths
path:
permission_handler: ^4.4.0+hotfix.4 # old ok
photo_view: ^0.9.2
provider: ^4.1.0
quiver: ^2.1.3
shared_preferences: ^0.5.7+1
sqflite: ^1.3.0+1
timezone: ^0.5.7
uuid: ^2.0.4
dev_dependencies:
flutter_test:
sdk: flutter
#moor_generator: # use the latest version
hive_generator: ^0.7.0+2
build_runner:
please can you make a tutorial of how can I test Hive in repplacement of Sharedpreferences in TDD series
i’m trying to do just that and i think the code goes inside the ‘datasources’ folder. But when I try to take the test I get this message “HiveError: You need to initialize Hive or provide a path to store the box.
” . Have you managed to implement?
This is my code test:
class MockBox extends Mock implements Box {}
class MockHiveInterface extends Mock implements HiveInterface {}
void main() {
late ArticleLocalDataSourceImpl dataSource;
late MockHiveInterface mockHiveInterface;
late MockBox mockBox;
setUp(() {
mockHiveInterface = MockHiveInterface();
mockBox = MockBox();
dataSource = ArticleLocalDataSourceImpl(box: mockBox);
});
group(‘getLastArticles’, () {
test(
‘should return Articles from DataBase when there is one in the cache’,
() async {
// arrange
when(() => mockHiveInterface.openBox(any()))
.thenAnswer((_) async => mockBox);
when(() => mockBox.get(‘listArticles’)).thenAnswer((_) => articlesMock);
// act
final result = await dataSource.getLastArticles();
// assert
verify(() => mockHiveInterface.openBox(any()));
verify(() => mockBox.get(‘listArticles’));
expect(result, articlesMock);
},
);
});
}
This is my main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDir.path);
runApp(MyApp());
}
And this is my pubspec:
dependencies:
flutter:
sdk: flutter
hive: ^2.0.1
hive_flutter: ^1.0.0
path_provider: ^2.0.1
dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^0.1.1
hive_generator: ^1.0.1
build_runner: ^1.12.2
At first, thank you so much for your hive Tutorial. It was very helpful to understand how Hive works.
But I have one question:
How can I update the outputs?
For example we have Jack Terry and he is 25 years old.
Now I want to rename his name to Jack Harris.
How can I do that?
I had an idea:
I create in the dart file “new_contact_form” two constructors. A default constructor and a second constructor called NewContactForm.update(String name, String age). Here is the code:
import ‘package:flutter/material.dart’;
import ‘package:hive/hive.dart’;
import ‘models/contact.dart’;
class NewContactForm extends StatefulWidget {
String name;
String age;
NewContactForm(){}
NewContactForm.update(String name, String age){
_NewContactFormState(name, age){
this.name = name;
this.age = age;
}
}
@override
_NewContactFormState createState() => _NewContactFormState();
}
class _NewContactFormState extends State {
final _formKey = GlobalKey();
String _name;
String _age;
void addContact(Contact contact){
//print(‘Name: ${contact.name}, Age: ${contact.age}’);
//Zum hinzufügen haben wir zwei Möglichkeiten:
//1. Möglichkeit:
//Hive.box(‘contacts’).put(‘contact1’, contact);
//2. Möglichkeit:
//Bei der Add Methode wird der Key automatisch inkrementiert
final contactsBox = Hive.box(‘contacts’);
contactsBox.add(contact);
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
Row(
children: [
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: ‘Name’),
onSaved: (value) => _name = value,
),
),
SizedBox(width: 10),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: ‘Age’),
keyboardType: TextInputType.number,
onSaved: (value) => _age = value,
),
),
],
),
RaisedButton(
child: Text(‘Add New Contact’),
onPressed: () {
_formKey.currentState.save();
final newContact = Contact(_name, int.parse(_age));
addContact(newContact);
},
),
],
),
);
}
}
On the contact_page.dart file I read this:
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
contactsBox.putAt(
index,
/*Contact(
‘${contact.name}’,
contact.age + 1,
),*/
NewContactForm.update(contact.name, contact.age.toString()),
);
},
),
But it doesn’t work. 🙁
I hope that someone can help me there.
Awesome article. Many thanks for you guy, Matt. <3
Hi.
I copied all the code and got this compiler error:
Compiler message:
lib/models/contact.g.dart:9:7: Error: The non-abstract class ‘ContactAdapter’ is missing implementations for these members:
– TypeAdapter.typeId
Hey reso thanks for this, is it possible to add some details on hive relationships?. The documentations might not be very clear
Hi,
with TDD in mind, how would you use Mockito with Hive.
class MockHive extends Mock implements
thank you for your help.
I have this error and can’t solve it
Unhandled Exception: MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider)
Could you put some code how to test app with Hive?
Thanks for sharing. I read many of your blog posts, cool, your blog is very good.
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.