Flutter Riverpod redirection after entity creation

Viewed 36

I'm working on my Flutter app and I try to set up a redirection process after creating an entity (user). The state management is handled by Riverpod. I'm using Firebase for the auth and Postgres for the database.

The insert method in the repository returns a User. With a StateNotifier I just want to check if the method returns a user. If the user is returned I set a success state object (CreateAccountStateSuccess), if it's not I set an error state object with a message. Problem: my saveUser method always return null in my StateNotifier, even though my user is persisted in Firebase and my database. I think it's a Riverpod issue. Any idea?

My repository:

  Future<AppUser?> saveUser(String email, String nickname, String role,
      String firstname, String lastname) async {
    try {
      connection.open().then((value) async {
        Future<List<Map<String, Map<String, dynamic>>>> result = connection.mappedResultsQuery(
          'insert into public.user(email,nickname,role,firstname,lastname) '
          'values(@emailValue,@nicknameValue,@roleValue,@firstnameValue,@lastnameValue) '
          'returning *',
          substitutionValues: {
            'emailValue': email,
            'nicknameValue': nickname,
            'roleValue': role,
            'firstnameValue': firstname,
            'lastnameValue': lastname,
          },
          allowReuse: true,
          timeoutInSeconds: 30,
        );
        result.then((value) {
          final userFromDataBase = value[0]['user']!;
          return AppUser(
              email: userFromDataBase['email'],
              nickname: userFromDataBase['nickname'],
              role: userFromDataBase['role'],
              firstname: userFromDataBase['firstname'],
              lastname: userFromDataBase['lastname']
          );
        });
      });
    } catch (e) {
      print(ErrorHandler(message: e.toString()));
      return null;
    }
    return null;
  }

My Firebase method to create user for Firebase and using my repository method:

  Future<AppUser?> registerWithEmailAndPassword(String email, String password, String nickname, String role, String firstname, String lastname) async {
    FirebaseApp app = await Firebase.initializeApp(
        name: 'Secondary', options: Firebase.app().options);
    try {
      UserCredential result =
      await FirebaseAuth.instanceFor(app: app).createUserWithEmailAndPassword(email: email, password: password);
      User? user = result.user;
      if (user == null) {
        throw Exception("No user found");
      } else {
        try {
          return await UserRepository(user.email!).saveUser(email, nickname, role, firstname, lastname);
        } on PostgreSQLException catch (e) {
          print('CATCH POSTGRES EXCEPTION');
          print(ErrorHandler(message: e.code.toString()));
        }
      }
    } on FirebaseException catch (e) {
      print('CATCH FIREBASE EXCEPTION');
      print(ErrorHandler(message: e.code.toString()));
    }
    return null;
  }

My controller:

class CreateAccountController extends StateNotifier<CreateAccountState> {
  CreateAccountController(this.ref) : super(const CreateAccountStateInitial());

  final Ref ref;

  void register(String email, String password, String nickname, String role, String firstname, String lastname) async {
    state = const CreateAccountStateLoading();
    try {
      await ref.read(authRepositoryProvider).registerWithEmailAndPassword(
        email,
        password,
        nickname,
        role,
        firstname,
        lastname
      ).then((user){
        user != null ? state = const CreateAccountStateSuccess() : state = const CreateAccountStateError('Something went wrong with the user creation in database');
      });
    } catch (e) {
      state = CreateAccountStateError(e.toString());
    }
  }
}

final createAccountControllerProvider =
StateNotifierProvider<CreateAccountController, CreateAccountState>((ref) {
  return CreateAccountController(ref);
});

My state objects :

class CreateAccountState extends Equatable {
  const CreateAccountState();

  @override
  List<Object> get props => [];
}

class CreateAccountStateInitial extends CreateAccountState {
  const CreateAccountStateInitial();

  @override
  List<Object> get props => [];
}

class CreateAccountStateLoading extends CreateAccountState {
  const CreateAccountStateLoading();

  @override
  List<Object> get props => [];
}

class CreateAccountStateSuccess extends CreateAccountState {
  const CreateAccountStateSuccess();

  @override
  List<Object> get props => [];
}

class CreateAccountStateError extends CreateAccountState {
  final String error;

  const CreateAccountStateError(this.error);

  @override
  List<Object> get props => [error];
}

My screen:

class CreateAccountScreen extends StatefulHookConsumerWidget {
  const CreateAccountScreen({Key? key}) : super(key: key);

  @override
  ConsumerState<CreateAccountScreen> createState() => _CreateAccountScreenState();
}

class _CreateAccountScreenState extends ConsumerState<CreateAccountScreen> {
  TextEditingController emailController = TextEditingController();
  TextEditingController passwordController = TextEditingController();
  TextEditingController nicknameController = TextEditingController();
  TextEditingController roleController = TextEditingController();
  TextEditingController firstnameController = TextEditingController();
  TextEditingController lastnameController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    ref.listen<CreateAccountState>(createAccountControllerProvider, ((previous, state) {
      if (state is CreateAccountStateError) {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text(state.error.toString()),
          backgroundColor: Colors.red,
        ));
      }
      print(state.toString());
      if (state is CreateAccountStateSuccess) {
        context.goNamed('/', params:
        {
          'screenName': 'users'
        });
      }
    }));

    return Scaffold(
      appBar: AppBar(
          title: const Text('Create an account'),
          elevation: 8.0,
          backgroundColor: Colors.deepOrangeAccent,
          actions: [
            TextButton.icon(
              icon: const Icon(
                Icons.logout_rounded,
                color: Colors.white,
              ),
              label: const Text('', style: TextStyle(color: Colors.white)),
              onPressed: () async {
                ref.read(loginControllerProvider.notifier).signOut();
              },
            ),
          ]
      ),
      body: Padding(
          padding: const EdgeInsets.all(10),
          child: ListView(
            children: <Widget>[
              Container(
                  alignment: Alignment.center,
                  padding: const EdgeInsets.all(10),
                  child: const Text(
                    'Ludocal 2',
                    style: TextStyle(
                        color: Colors.deepOrange,
                        fontWeight: FontWeight.w500,
                        fontSize: 30),
                  )),
              Container(
                padding: const EdgeInsets.all(10),
                child: TextFormField(
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter an email" : null,
                  controller: emailController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Email Address',
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
                child: TextFormField(
                  obscureText: true,
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter a password" : null,
                  controller: passwordController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Password',
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
                child: TextFormField(
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter a nickname" : null,
                  controller: nicknameController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Nickname',
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
                child: TextFormField(
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter a role" : null,
                  controller: roleController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Role',
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
                child: TextFormField(
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter a firstname" : null,
                  controller: firstnameController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Firstname',
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 0),
                child: TextFormField(
                  validator: (value) =>
                  value == null || value.isEmpty ? "Enter a lastname" : null,
                  controller: lastnameController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Lastname',
                  ),
                ),
              ),
              Container(
                  height: 50,
                  padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
                  margin: const EdgeInsets.only(top:20),
                  child: ElevatedButton(
                    child: const Text('Create', style: TextStyle(color: Colors.white)),
                    onPressed: () {
                      ref
                          .read(createAccountControllerProvider.notifier)
                          .register(emailController.text, passwordController.text, nicknameController.text,
                      roleController.text, firstnameController.text, lastnameController.text);
                    },
                  )),
            ],
          )),
    );
  }
}
1 Answers

The problem is in the saveUser function. Instead of using .then use await. It would be like the following:

Future<AppUser?> saveUser(String email, String nickname, String role,
    String firstname, String lastname) async {
  try {
    await connection.open();
    final result = await connection.mappedResultsQuery(
      'insert into public.user(email,nickname,role,firstname,lastname) '
      'values(@emailValue,@nicknameValue,@roleValue,@firstnameValue,@lastnameValue) '
      'returning *',
      substitutionValues: {
        'emailValue': email,
        'nicknameValue': nickname,
        'roleValue': role,
        'firstnameValue': firstname,
        'lastnameValue': lastname,
      },
      allowReuse: true,
      timeoutInSeconds: 30,
    );

    final userFromDataBase = result[0]['user']!;
    return AppUser(
      email: userFromDataBase['email'],
      nickname: userFromDataBase['nickname'],
      role: userFromDataBase['role'],
      firstname: userFromDataBase['firstname'],
      lastname: userFromDataBase['lastname'],
    );
  } catch (e) {
    print(ErrorHandler(message: e.toString()));
    return null;
  }
}
Related