I’ve been building a basic app over the last few weeks and having tried using Firebase and Provider, I’ve now settled on AWS Amplify and Riverpod. However, I’m having an issue where instead of returning a value from a Provider, Flutter displays Instance of 'Future<String?'
. I’ve checked other questions of a similar nature, but I’ve not been able to figure out how to solve the issue in my situation.
I’m simply wishing to display the email of the authenticated user (from Amplify Auth) and using Riverpod. Code extracts below.
In the file where I’m seeking to display the email:
class MainDrawer extends ConsumerStatefulWidget {
const MainDrawer({super.key});
@override
ConsumerState<MainDrawer> createState() => _MainDrawerState();
}
class _MainDrawerState extends ConsumerState<MainDrawer> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final userEmail = ref.watch(authServiceProvider).getUserEmail();
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
UserAccountsDrawerHeader(
accountName: Text('Username here',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
accountEmail: Text(userEmail.toString()),
...
The file where the provider is setup:
final authServiceProvider = Provider<AuthService>((ref) {
return AuthService();
});
class AuthService {
AuthService();
Future<String?> getUserEmail() async {
try {
final result = await Amplify.Auth.fetchUserAttributes();
for (final element in result) {
if(element.userAttributeKey.toString() == 'email') {
print(element.value.toString());
return element.value;
}
}
} on Exception catch (e) {
debugPrint(e.toString());
rethrow;
}
return null;
}
Future<void> signOut() async {
try {
await Amplify.Auth.signOut();
await Amplify.DataStore.clear();
} on Exception catch (e) {
debugPrint(e.toString());
}
}
}
I can also confirm that Amplify Auth is working correctly (I can sign up/sign in/log out etc.) and you’ll see I print out the email to the console and it displays correctly.
I’ve also got Riverpod working with ProviderScope above the app:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
bool isAmplifySuccessfullyConfigured = false;
try {
await _configureAmplify();
isAmplifySuccessfullyConfigured = true;
} on AmplifyAlreadyConfiguredException {
print('Amplify configuration failed.');
}
runApp(
ProviderScope(
child: MyApp(),
),
);
}
Based on the other answers I’ve read, the issue is something to do with the fact that getUserEmail() is asynchronous. But, I also thought that ref.watch meant that the UI would rebuild when the value changed? Should I be using a different type of Riverpod ‘Provider’? Or am I going wrong somewhere else?
Any help getting to the bottom of why the value doesn’t display properly and the best approach to having it display, I’d be most grateful.
2
Answers
The method
Future<String?> getUserEmail()
returns a future, you need to await until the fetch is complete, You can use FutureBuilder for this.Example with two method
This is happening because you’re calling asynchronous data. Instead of bothering using
FutureBuilder
from Flutter, use aFutureProvider
from Riverpod and exploit its API.TL;DR: wrap the
getUserEmail
call inside aFutureProvider
. When youwatch
aFutureProvider
, you’ll get anAsyncValue<String>
.AsyncValue
has a handy method calledwhen
that you can exploit to show asynchronous data to the user.See the documentation to understand how Riverpod, FutureProvider and AsyncValue work.
Further detail from the question poser to show what worked for anyone else who might be struggling with the learning curve and is keen to see some fuller code in situ:
The Providers file (note also addition of autoDispose):
And then in the file where the content is displayed:
This then delivered the result of displaying the email address.