Hive: Model`s children are saved correctly to hive only on next run

Created on 16 Jan 2020  路  4Comments  路  Source: hivedb/hive

Question
When the new object is created and added into the flashcardsBox it saves all the fields except frontImages and backImages lists. But when I restart the app these fields look like being saved. How to fix that such that all the object's fields will be added to a box immediately after flashcardsBox.add(flashcard) was called?

image
after next run:

image

Code sample

Flashcard model:

import 'package:hive/hive.dart';
import 'package:my_flashcards/models/deck.dart';

part 'flashcard.g.dart';

@HiveType(typeId: 1)
class Flashcard {
  @HiveField(0)
  final String frontText;

  @HiveField(1)
  List<String> frontImages;

  @HiveField(2)
  final String backText;

  @HiveField(3)
  List<String> backImages;

  @HiveField(4)
  final Deck deck;

  @HiveField(5)
  final int numberOfRepetitions;

  @HiveField(6)
  final double eFactor;

  @HiveField(7)
  final int quality;

  @HiveField(8)
  final int interval;

  @HiveField(9)
  final DateTime nextReviewDate;

  Flashcard(
      {this.frontText,
      this.frontImages,
      this.backText,
      this.backImages,
      this.deck,
      this.numberOfRepetitions,
      this.eFactor,
      this.quality,
      this.interval,
      this.nextReviewDate});
}

How it is saved:

final flashcard = Flashcard(
        frontText: frontFormTextEditingController.text,
        frontImages: frontImagesPaths,
        backText: backFormTextEditingController.text,
        backImages: backImagesPaths,
        deck: selectedDeckBox.getAt(0),
        numberOfRepetitions: 0,
        eFactor: 2.5,
        quality: 4,
        nextReviewDate: DateTime.now(),
        interval: 0);
    flashcardsBox.add(flashcard);

_where frontImagesPaths and backImagesPaths are Lists of String_

How it is showed:

class FlashcardsList extends StatefulWidget {
  @override
  _FlashcardsListState createState() => _FlashcardsListState();
}

class _FlashcardsListState extends State<FlashcardsList> {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: flashcardsBox.listenable(),
      builder: (BuildContext context, Box<Flashcard> box, Widget child) {
        List<Flashcard> flashcards = box.values.toList();
        return SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) => FlashcardSmall(
                flashcard: flashcards[flashcards.length - index - 1]),
            childCount: flashcards.length,
          ),
        );
      },
    );
  }
}

Version

  • Platform: iOS, Android
  • Flutter version: 1.12.13
  • Hive version: 1.3.0
  • hive_flutter: 0.3.0+1
question

All 4 comments

I need to test this, thanks for letting me know.

I need to test this, thanks for letting me know.

I've tested the issue and probably there are no problems with Hive in this case, every list of strings was correctly added to the model and was shown immediately after adding to box.

So problem may be with the way I'm picking an Image from gallery and adding the list of images paths to model's field. I wrote the simplest code to show the issue. And the result is:

_After adding to box:_
image
_After rerun_
image

So that's the code:

pubspec.yaml

name: issue
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  hive: ^1.4.0+1
  hive_flutter: ^0.3.0+1
  image_picker: ^0.6.3+1

dev_dependencies:
  hive_generator: ^0.7.0
  build_runner: ^1.7.4
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

main.dart

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:issue/view.dart';

import 'model.dart';

void main() async {
  await initHive();
  runApp(View());
}

Future<void> initHive() async {
  await Hive.initFlutter();
  Hive.registerAdapter<Model>(ModelAdapter());
  await Hive.openBox<Model>('modelsBox');
}

model.dart

import 'package:hive/hive.dart';

part 'model.g.dart';

@HiveType(typeId: 0)
class Model {
  @HiveField(0)
  final List<String> items;

  Model({this.items});
}

//  flutter packages pub run build_runner watch --delete-conflicting-outputs

pick_image.dart

import 'dart:async';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';

class ImagesComponent {
  Directory appDocumentsDir;
  String savingPath;
  ImagesComponent() {
    init();
  }

  void init() async {
    appDocumentsDir = await getApplicationDocumentsDirectory();
    // getting a directory path for saving
    savingPath = appDocumentsDir.absolute.path;
  }

  Future<String> pickImageAndGetPath(ImageSource source) async {
    File imageFile =
        await ImagePicker.pickImage(source: source, imageQuality: 100);

//    Handling Main Activity destruction on Android
    if (imageFile == null) {
      print('retrieving lost data');
      imageFile = await retrieveLostData();
    }

    return imageFile.path;
  }

  //        Handling MainActivity destruction on Android
  Future<File> retrieveLostData() async {
    final LostDataResponse response = await ImagePicker.retrieveLostData();
    return response.file;
  }
}

view.dart

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:issue/pick_image.dart';

import 'model.dart';

class View extends StatefulWidget {
  @override
  _ViewState createState() => _ViewState();
}

class _ViewState extends State<View> {
  var imagesComponent = ImagesComponent();
  List<String> imagePaths = [];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Issue example'),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () async {
//            pick image from gallery
            String imagePath =
                await imagesComponent.pickImageAndGetPath(ImageSource.gallery);

//            fill the list three times with selected image's path
            for (int i = 0; i < 3; i++) {
              imagePaths.add(imagePath);
            }

            await Hive.box<Model>('modelsBox').add(Model(items: imagePaths));

            imagePaths.clear();
          },
        ),
        body: ValueListenableBuilder(
          valueListenable: Hive.box<Model>('modelsBox').listenable(),
          builder: (BuildContext context, Box box, Widget child) {
            var models = box.values.toList().reversed.toList();
            return ListView.builder(
              itemCount: models.length,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                    title: Text(
                        '${models.length - index} ${models[index].items.toString()}'));
              },
            );
          },
        ),
      ),
    );
  }
}

Where could the problem be? @leisim

Here is the problem:

await Hive.box<Model>('modelsBox').add(Model(items: imagePaths));

imagePaths.clear()

When you add (or put) an object to Hive, it will not be copied but used as-is. That means if you alter the object instance from somewhere, you also change the object that Hive has in memory. This does not affect the persisted data.

You clear the imagePaths list so the Model instance you added to Hive also has an empty list. When you restart the app, a new, filled list is loaded from the disk.
Replace the code above with this and it should work:

await Hive.box<Model>('modelsBox').add(Model(items: imagePaths.toList()));

imagePaths.clear()

Now each Model instance has its own list instance.

Here is the problem:

await Hive.box<Model>('modelsBox').add(Model(items: imagePaths));

imagePaths.clear()

When you add (or put) an object to Hive, it will not be copied but used as-is. That means if you alter the object instance from somewhere, you also change the object that Hive has in memory. This does not affect the persisted data.

You clear the imagePaths list so the Model instance you added to Hive also has an empty list. When you restart the app, a new, filled list is loaded from the disk.
Replace the code above with this and it should work:

await Hive.box<Model>('modelsBox').add(Model(items: imagePaths.toList()));

imagePaths.clear()

Now each Model instance has its own list instance.

Thanks a lot. It works now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NourEldinShobier picture NourEldinShobier  路  3Comments

Ferdzzzzzzzz picture Ferdzzzzzzzz  路  3Comments

kthecoder picture kthecoder  路  3Comments

jamesdixon picture jamesdixon  路  3Comments

aminjoharinia picture aminjoharinia  路  3Comments