Search code examples
flutterflutter-webflutter-hive

Flutter Hive opens existing box, but is not reading values from it


I am having one of those programming moments where I think I am going mad so hopefully someone can help me.

I have a Flutter app that uses Hive to store data between runs. When the app initially starts, it opens a box and retrieves some information to set the saved theme for the MaterialApp. It then builds the main page for the app and retrieves a range of other options. This was working perfectly (I have a version of it on my phone that works perfectly), but it has stopped working for some reason.

When the app executes, the initial MyApp states that the Hive box is open, but it has no values in it. This is true for a call to an options class to retrieve the options data. After that call, the box suddenly does have values and I am able to retrieve and print out the keys. When the app then builds the main page, it states that the box is open and it has values and is able to retrieve the options data from the options class. Previously, I have had no problems with the first reading of data to extract the theme. I have posted the relevant sections of code below long with the print output from a run.

I am running the app in web and have also run it on a mobile emulator. It has previously worked fine on both platforms, but is now not working on the web platform. It appears to be working fine on the mobile emulator.

The app is using the following versions:

Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision c860cba910 (6 days ago) • 2022-03-25 00:23:12 -0500
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2

pubspec.yaml dependencies:
hive: ^2.0.6
hive_flutter: ^1.1.0 

I have upgraded to the latest version of Flutter today to see if that fixed the problem. I had the same issue on the previous stable release.

I have updated to hive 2.1.0 and get the same problem/output.

I have also tried downgrading Flutter to 2.10.0 with Dart 2.16.0, which I know worked fine, and that hasn't solved the problem.

main.dart

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'package:lettercloud/data/colours.dart';
import 'package:lettercloud/options/option_page.dart';

const String _boxName = 'lettercloud';

void main() async {
  await Hive.initFlutter();
  Hive
    ..registerAdapter(CellAdapter())
    ..registerAdapter(ThemeModeOptionAdapter());
  await Hive.openBox(_boxName);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  final Box _box = Hive.box(_boxName); // Object for hive data access
  final Options _options = Options();
  final Colours _colours = Colours();
  late bool _firstRun = true; // Flag to only read Hive options on first run

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    print('build() Before first run.  Extracting box keys. Attempt 1...');
    for (String key in _box.keys) {
      print('box keys: $key');
    }

    if (_firstRun) {
      print(
          'First run.  Hive box is open: ${_box.isOpen}  Box has values: ${_box.isNotEmpty}');
      _options.setHiveBox(_box); // Pass hive object and retrieve options
      _firstRun = false;
    }
    print('');
    print('build() After first run.  Extracting box keys. Attempt 2...');
    for (String key in _box.keys) {
      print('box keys: $key');
    }

    return AnimatedBuilder(
        animation: _options.getThemeNotifier(),
        builder: (context, child) {
          return MaterialApp(
            title: 'Lettercloud',
            theme: FlexThemeData.light(scheme: FlexScheme.jungle),
            darkTheme: FlexThemeData.dark(scheme: FlexScheme.jungle),
            themeMode: _options.getThemeMode(),
            home: ResponsiveSizer(
              builder: (context, orientation, screenType) {
                return const MyPage(title: 'Lettercloud Anagram Helper');
              },
            ),
          );
        });
  }
}

class MyPage extends StatefulWidget {
  const MyPage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyPage> createState() => MyPageState();
}

class MyPageState extends State<MyPage> {
  final Options _options = Options();
  late final Box _box; // Object for hive data access
  late Widget _displayGrid;

  @override
  void initState() {
    super.initState();
    print('Doing init MyPageState');
    _box = Hive.box(_boxName);
    _options.setHiveBox(_box); // Pass hive object and retrieve options
    _setGrid(_options.getGridType());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Lettercloud'),
      ),
      resizeToAvoidBottomInset: false, // Overlay on-screen keyboard
      body: SafeArea(
        child: _displayGrid,
      ),
    );
  }

  // Set the grid to display based on the grid type option
  void _setGrid(GridType type) {
    _displayGrid = _options.getGridType() == GridType.square
        ? GridSquare(box: _box, options: _options, update: updateGrid)
        : GridDiamond(box: _box, options: _options, update: updateGrid);
  }

  // Callback to set the grid type if the option changes
  void updateGrid(GridType type) {
    setState(() {
      _setGrid(type);
    });
  }
}

options.dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:hive_flutter/hive_flutter.dart';

class Options {
  bool _lightMode = true; // Use light colours, or dark
  static const String _lightModeName = 'lightMode';

  bool _showGrid = true; // Show grid around tiles, or not
  static const String _showGridName = 'showGrid';

  bool _firstEdit =
      true; // Flag to show edit on first start, doesn't need saving
  bool _editOnStart = false; // Show edit at startup, or not
  static const String _editOnStartName = 'editOnStart';

  CharType _charType = CharType.mixed; // Type of letters to show
  static const String _charTypeName = 'charType';

  ThemeModeOption _themeMode = ThemeModeOption()..setMode(ThemeMode.light);
  static const String _themeModeName = 'themeMode';

  late Box _box; // Hive object
  late final double _tabletF; // Reduction factor for tablet displays

  late GridType _gridType = GridType.square;
  static const String _gridTypeName = 'gridType';

  late GridType _savedGridType = _gridType;
  static const String _savedGridTypeName = 'savedGridType';

  // last page name - used to control text entry on startup
  String _lastPage = PageName.main.toString();
  static const String _lastPageName = 'lastPageName';

  // Flag to show if the grid type has change.  Used to prevent 'show on start'
  // triggering the text entry box after the grid layout has been changed by the user
  bool _backFromOptionsPage = false;
  final String _backFromOptionsPageName = 'fromOptions';

  ///
  /// Hive management methods and global options setting
  ///
  void setHiveBox(Box b) {
    _box = b; // Pass the hive management object
    print(
        'Options hive box. Box is open: ${_box.isOpen}  Box has values: ${_box.isNotEmpty}.');
    // Set screen size factor for web vs tablet
    if (kIsWeb) {
      _tabletF = 0.4; // Factor components by 0.4 for web
    } else {
      _tabletF = 0.6; // Factor components by 0.6 for tablets
    }
    // Retrieve any option data values
    if (_box.get(_lightModeName) != null) {
      _lightMode = _box.get(_lightModeName);
    } else {
      print('Cannot find $_lightModeName');
      _box.put(_lightModeName, _lightMode);
    }
    if (_box.get(_showGridName) != null) {
      _showGrid = _box.get(_showGridName);
    } else {
      _box.put(_showGridName, _showGrid);
    }
    if (_box.get(_editOnStartName) != null) {
      _editOnStart = _box.get(_editOnStartName);
    } else {
      _box.put(_editOnStartName, _editOnStart);
    }
    if (_box.get(_charTypeName) != null) {
      String temp = _box.get(_charTypeName);
      _charType = getCharEnum(temp);
    } else {
      _box.put(_charTypeName, _charType.toString());
    }
    if (_box.get(_themeModeName) != null) {
      _themeMode = _box.get(_themeModeName);
    } else {
      _box.put(_themeModeName, _themeMode);
    }
    if (_box.get(_gridTypeName) != null) {
      String temp = _box.get(_gridTypeName);
      _gridType = getGridEnum(temp);
    } else {
      _box.put(_gridTypeName, _gridType.toString());
    }
    if (_box.get(_savedGridTypeName) != null) {
      String temp = _box.get(_savedGridTypeName);
      _savedGridType = getGridEnum(temp);
    } else {
      _box.put(_savedGridTypeName, _savedGridType.toString());
    }
    if (_box.get(_backFromOptionsPageName) != null) {
      _box.put(_backFromOptionsPageName, _backFromOptionsPage);
    } else {
      _box.put(_backFromOptionsPageName, _backFromOptionsPage);
    }
    // Load last page value or reset if doesn't exit
    if (_box.get(_lastPageName) != null) {
      _box.put(_lastPageName, _lastPage);
    } else {
      _box.put(_lastPageName, _lastPage);
    }
    _box.flush(); // Make sure everything is written to the disk
  }
}

Command line output:

flutter run -d chrome --web-renderer html --web-port 5555
Launching lib\main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome...             18.6s
This app is linked to the debug service: ws://127.0.0.1:54752/JAXqfQgauf4=/ws
Debug service listening on ws://127.0.0.1:54752/JAXqfQgauf4=/ws

 Running with sound null safety

  To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

An Observatory debugger and profiler on Chrome is available at: http://127.0.0.1:54752/JAXqfQgauf4=
The Flutter DevTools debugger and profiler on Chrome is available at:
http://127.0.0.1:9101?uri=http://127.0.0.1:54752/JAXqfQgauf4=

build() Before first run.  Extracting box keys. Attempt 1...
First run.  Hive box is open: true  Box has values: false
Options hive box. Box is open: true  Box has values: false.
Cannot find lightMode

build() After first run.  Extracting box keys. Attempt 2...
box keys: charType
box keys: editOnStart
box keys: fromOptions
box keys: gridType
box keys: lastPageName
box keys: lightMode
box keys: savedGridType
box keys: showGrid
box keys: themeMode
Doing init MyPageState
Options hive box. Box is open: true  Box has values: true.
Application finished.

Update #1 Since originally posting I have tried deleting the box and re-running the app in case this was caused by a corrupt file. That hasn't made any difference.

I have also tried adding a .then to the openBox() command in case this is yet another async programming issue, but that hasn't made a difference either, i.e.

  await Hive.openBox(_boxName).then((value) {
    print('value is $value');
    runApp(MyApp());
  });

Update #2 So, it took me a while to work this out, but I create my box values on the first run if they don't already exist (to address the use case of the first ever run of the app). If I remove all the put statements in the setHiveBox() method then I get the problem consistently. In other words, there are no values in the box until my Options class creates them when the app runs. This suggests that the data is not being saved to the disk by app. I have compared both main.dart and options.dart with last known working versions and can't see any obvious differences. What could stop the application from saving the data to the disk? Just to note, I have tested another app I developed that uses Hive and this continues to work perfectly. That uses the same version of Hive as this app does.


Solution

  • I fixed this by doing a flutter clean on the project, deleting the flutter installation (deleting the install folder from the disk completely), downloading and re-installing flutter and then doing a flutter pub get on the project folder.

    I had previously tried a flutter clean and flutter pub get on their own and this didn't fix the problem so maybe something had gone wrong in the flutter folder itself after the last upgrade? Anyway, a clean install of everything has solved the problem.