skip to Main Content

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


  1. The method Future<String?> getUserEmail() returns a future, you need to await until the fetch is complete, You can use FutureBuilder for this.

    late final userEmail = ref.watch(authServiceProvider).getUserEmail();
    
      @override
      Widget build(BuildContext context) {
      ...
      FutureBuilder(
          future: userEmail,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return Text(
                'Username here',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              );
            } else {
              return Text("No data");
            }
          })
    

    Example with two method

    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    
    final authServiceProvider = Provider<AuthService>((ref) {
      return AuthService();
    });
    
    final emailProvider = FutureProvider<String?>((ref) async {
      final authService = ref.watch(authServiceProvider);
      return await authService.getUserEmail();
    });
    
    class AuthService {
      AuthService();
      Future<String?> getUserEmail() async {
        return await Future.delayed(Duration(seconds: 2))
            .then((value) => "Dolores");
      }
    }
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      bool isAmplifySuccessfullyConfigured = false;
    
      runApp(
        ProviderScope(
          child: MaterialApp(home: MyApp()),
        ),
      );
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: MainDrawer(),
        );
      }
    }
     
    
    class MainDrawer extends ConsumerStatefulWidget {
      const MainDrawer({super.key});
    
      @override
      ConsumerState<MainDrawer> createState() => _MainDrawerState();
    }
    
    class _MainDrawerState extends ConsumerState<MainDrawer> {
      @override
      void initState() {
        super.initState();
      }
    
      late final userEmail = ref.read(authServiceProvider).getUserEmail();
    
      @override
      Widget build(BuildContext context) {
        return Drawer(
          child: ListView(
            padding: EdgeInsets.zero,
            children: [
              ref.watch(emailProvider).when(
                  data: (email) => Text(email ?? "No data"),
                  loading: () => Text("Loading"),
                  error: (error, stack) => Text("Error")),
              FutureBuilder(
                  future: userEmail,
                  builder: (context, snapshot) {
                    if (snapshot.hasData) {
                      return Text(
                        '${snapshot.data}',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      );
                    } else {
                      return Text("No data");
                    }
                  })
            ],
          ),
        );
      }
    }
    
    
    Login or Signup to reply.
  2. This is happening because you’re calling asynchronous data. Instead of bothering using FutureBuilder from Flutter, use a FutureProvider from Riverpod and exploit its API.

    TL;DR: wrap the getUserEmail call inside a FutureProvider. When you watch a FutureProvider, you’ll get an AsyncValue<String>. AsyncValue has a handy method called when 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):

    final fetchUserEmailProvider = FutureProvider.autoDispose<String>((ref) {
      return fetchUserEmail();
    });
    
    Future<String> fetchUserEmail () async {
      final result = await Amplify.Auth.fetchUserAttributes();
      for (final element in result) {
        if(element.userAttributeKey.toString() == 'email') {
          return element.value.toString();
        }
      }
      return 'No data';
    }
    

    And then in the file where the content is displayed:

    Widget build(BuildContext context) {
      final userEmail = ref.watch(fetchUserEmailProvider);
      return Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
            children: [
              UserAccountsDrawerHeader(
                accountName: Text('Username here',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              accountEmail: userEmail.when(
                  loading: () => Text('Loading...'),
                  error: (err, stack) => Text('Error: $err'),
                  data: (userEmail) {
                    return Text(userEmail.toString());
                  }),
    ....
    

    This then delivered the result of displaying the email address.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search