Hive (Flutter Tutorial) – Lightweight & Fast NoSQL Database

32  comments

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 TypeAdapters. 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 Boxes, 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.

If you have a lot of data inside a 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.

Before your application exits, you should call 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.

Keys must be either of type 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:

flutter packages pub run build_runner build
There are some precautions you should take when updating a class with a generated 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.

  1. Register the TypeAdapter just for a single Box.
  2. Register it for all the Boxes.

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 BoxEvents. This is plenty enough if you have a proper state management, for example with Bloc, where you don't expose Boxes 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:

Hive is an append-only data store. When you change or delete a value, the change is written to the end of the box file. This leads sooner or later to a growing box file. Hive may automatically “compact” your box at any time.

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 Boxes, 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.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • 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

      • 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 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

  • 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?

      • 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;

  • 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.

  • 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)

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >