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();
},
),
);
}
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();
},
),
);
}