Otabek Ochilov
Otabek Ochilov

Reputation: 53

Flutter RepositoryProvider and Hive LateInitializationError

I have app where I am using Bloc and Hive.

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final appDocumentDirectory =
      await path_provider.getApplicationDocumentsDirectory();
  Hive.init(appDocumentDirectory.path);

  runApp(
    const MyApp(),
  );
}

On MyApp widget registered MultiRepositoryProvider

return MultiRepositoryProvider(
  providers: [
    RepositoryProvider(create: (context) => AccountService()),
  ],
  child: MultiBlocProvider(
    providers: [
      BlocProvider<AccountBloc>(
        create: (context) => AccountBloc(context.read<AccountService>()),
      ),
    ],
    child: MaterialApp(
      home: const AppPage(),
    ),
  ),
);

AppPage Contains bottomNavigationBar and some pages

account.dart

class AccountService {
  late Box<Account> _accounts;
  AccountService() {
    init();
  }

  Future<void> init() async {
    Hive.registerAdapter(AccountAdapter());
    _accounts = await Hive.openBox<Account>('accounts');
  }

On appPage have BlocBuilder

BlocBuilder<AccountBloc, AccountState>(
builder: (context, state) {
  if (state.accountStatus == AccountStatus.loading) {
    return const CircularProgressIndicator();
  } else if (state.accountStatus == AccountStatus.error) {
    Future.delayed(Duration.zero, () {
      errorDialog(context, state.error);
    });
  }
  return SingleChildScrollView(....

When app first loaded I receive LateInitializationError that late Box <Account> _accounts from account Repository not initialized. But as soon as I navigate to another page and go back, the Box <Account> _accounts are initialized and the data appears. How can I avoid this error and initialize the Hive box on application load?

Upvotes: 1

Views: 940

Answers (2)

X.J.L
X.J.L

Reputation: 65

It's been like 7 months, but if you are still looking for an answer, not sure if it's optimal but below should work.

My understanding on the issue you are having is that the reason why there is that "LateInitializationError" is because that your init function call in your constructor is asynchronously invoked without await for its result. As a result, there is a possibility that when you are calling functions on the box, the initialisation is not yet finished. When you navigate to another page and go back, the function init run happened to be finished. Hence, the error is gone. The complexity here is that constructor can not be marked as async for you to use that await keyword. Since you are using bloc, one possible workaround is to call the init function of your repo when bloc is in init state.

For demo purpose I defined below bloc states and events, you can absolutely change them based on your needs.

// bloc states
abstract class AccountState{}

class InitState extends AccountState{}

class LoadedState extends AccountState{
LoadedState(this.accounts);
final List<Account> accounts;
}

class LoadingErrorState  extends AccountState{}

//bloc events
abstract class AccountEvent {}

class InitEvent extends AccountEvent {}
... // other events

in your bloc logic you can call the init function from you repo on InitEvent

class AccountBloc extends Bloc<AccountEvent, AccountState> {
  AccountBloc(this.repo) : super(InitState()) {
    on<InitEvent>((event, emit) async {
      await repo.init();
      emit(LoadedState(account: repo.getAccounts()));
    });
    ...// define handlers for other events
  }
  final AccountRepository repo;
}

in your service class you can remove the init from the constructor like:

class AccountService {
  late Box<Account> _accounts;
  AccountService();

  Future<void> init() async {
    Hive.registerAdapter(AccountAdapter());
    _accounts = await Hive.openBox<Account>('accounts');
  }
 
  List<Account> getAccounts(){
    return _accounts.values.toList();
  }
}

Then in your bloc builder, you can add init event to your bloc when the state is InitState as below:

BlocBuilder<AccountBloc, AccountState>(
builder: (context, state) {
  if (state is InitState) {
    context.read<AccountBloc>.add(InitEvent());
    return const CircularProgressIndicator();
  } else if (state is LoadingErrorState) {
    Future.delayed(Duration.zero, () {
      errorDialog(context, state.error);
    });
  }
  else if (state is LoadedState){
  return SingleChildScrollView(....
  }

Also, FYI, you can if you want the init to be called when the object of your account service is instantiated, you can take a look at below answer: https://stackoverflow.com/a/59304510/16584569

However, you still going to need to await for the initialisation of your service. One possible way is just do it in your main function and pass down to your app, but it makes the structure of your code messy and when you want to swap to another repo, you need to remember to change code in main function as well.

Upvotes: 0

Yunus Emre &#199;elik
Yunus Emre &#199;elik

Reputation: 326

Can you try this? I think you need to await Hive init function

 void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final appDocumentDirectory =
      await path_provider.getApplicationDocumentsDirectory();
  await Hive.init(appDocumentDirectory.path);

  runApp(
    const MyApp(),
  );
}

Upvotes: 0

Related Questions