flutterdartasync-awaitflutter-cubit

Is it possible to await a create function of a BlocProvider in Flutter?


I have a page where users can fill in their personal credentials. Once they’ve entered them, the credentials should be saved and automatically appear the next time the page is opened.

I'm managing the credentials using a CustomerCubit, which loads and saves the data with SharedPreferences. Since loading requires an asynchronous call to getStringList, I use await inside the Cubit's init() method.

So here is the CustomerCubit code of the init() method:

void init() async {
final prefs =
    await SharedPreferences.getInstance(); // <== It needs to await here
List<String>? personalDetails = prefs.getStringList("personalDetails");
if (personalDetails != null) {
  emit(
    CustomerState(
      firstName: personalDetails[0],
      surName: personalDetails[1],
      street: personalDetails[2],
      streetNo: personalDetails[3],
      plz: int.parse(personalDetails[4]),
      city: personalDetails[5],
      salutation: personalDetails[6],
      email:
          (personalDetails[7] == "")
              ? null
              : personalDetails[7], //From https://dart.dev/language/operators#conditional-expressions
    ),
  );
}

}

And here is the important part from the CredentialPage:

@override
Widget build(BuildContext context) {
  return BlocProvider(
    create: (context) => CustomerCubit()..init(),   // <== Then it awaited the prefs.getStringList() and the values would be ready..
    child: Builder(
      builder: (context) {
        _firstName.text =
            BlocProvider.of<CustomerCubit>(context).state.firstName; //   <=== It firstly does this
        _surName.text = BlocProvider.of<CustomerCubit>(context).state.surName;  //  <==┘
        _street.text = BlocProvider.of<CustomerCubit>(context).state.streetNo;  //  <==┘
        _plz.text =
            BlocProvider.of<CustomerCubit>(context).state.plz.toString();       //    .
        _city.text = BlocProvider.of<CustomerCubit>(context).state.city;        //    .
        _email.text =
            BlocProvider.of<CustomerCubit>(context).state.email ?? "";

        return Container();
      },
    ),
  );
}

Solution

  • You can't await in the create method of the BlocProvider. Doing so would either cause the main app thread to hang (resulting in a frozen app) or result in a state where the BlocProvider doesn't know what it's supposed to draw to the screen.

    Instead, put an isInitialized field in your state that defaults to false. In your provider's builder, check the isInitialized to see of the initialization has happened yet, and if not, display a loading screen of some variety. Then set this field to true at the end of your cubit's initialization and trigger a rebuild.

    void init() async {
      final prefs = await SharedPreferences.getInstance();
    
      List<String>? personalDetails = prefs.getStringList("personalDetails");
      if (personalDetails != null) {
        emit(
          CustomerState(
            isInitialized: true, // Add this new field to your CustomerState and have it be false by default
            firstName: personalDetails[0],
            surName: personalDetails[1],
            street: personalDetails[2],
            streetNo: personalDetails[3],
            plz: int.parse(personalDetails[4]),
            city: personalDetails[5],
            salutation: personalDetails[6],
            email:
                (personalDetails[7] == "")
                    ? null
                    : personalDetails[7], //From https://dart.dev/language/operators#conditional-expressions
          ),
        );
      } else {
        // If personalDetails fails to load, do something here to set isInitialized to true
        // Otherwise the loading screen will never go away
      }
    }
    
    @override
    Widget build(BuildContext context) {
      return BlocProvider(
        create: (context) => CustomerCubit()..init(),
        child: Builder(
          builder: (context) {
            // Just get this once to reduce unnecessary tree-crawling calls and typing
            final state = BlocProvider.of<CustomerCubit>(context).state;
    
            if (!state.isInitialized) {
              // The state is still initializing, so show a temporary loading screen
              return Center(
                child: CircularProgressIndicator(),
              );
            }
    
            _firstName.text = state.firstName; 
            _surName.text = state.surName;  
            _street.text = state.streetNo;  
            _plz.text = state.plz.toString();         
            _city.text = state.city;        
            _email.text = state.email ?? "";
    
            return Container();
          },
        ),
      );
    }