Flutter Navigator pushnamed object not working when sent to class as parameter - flutter

I am trying to refactor my code and use a separate class object to register users and then send them to the correct page which is either the awaiting for email confirmation screen(if they haven't confirmed their email address) or the actual chat screen in the app.
I pass the Navigator.pushNamed object from the Flutter material build to the separate class object, but the Navigator.pushNamed doesn't work when it is sent as a parameter to the class object. It only worked when it is in the main build.
This is the main section that collects the parameters and sends them to the class
Widget build(BuildContext context) {...
Registration(
email: email,
password: password,
awaitingEmailConfPage:
() async {
await Navigator.pushNamed(context, EmailConfirmation.id);
},
confirmedEmailConfPage: () async {
await Navigator.pushNamed(context, ChatScreen.id);
}
).registerNewUser();
This is the class object that receives the Navigator.pushNamed but doesn't want to allow it to work and send users to the correct screen.
class Registration {
Registration(
{required this.awaitingEmailConfPage,
required this.confirmedEmailConfPage});
final _auth = FirebaseAuth.instance;
Future Function() awaitingEmailConfPage;
Future Function() confirmedEmailConfPage;
void registerNewUser() async {
try {
User? user = _auth.currentUser;
if (user!.emailVerified) {
await awaitingEmailConfPage;
} else {
await confirmedEmailConfPage;
}
}
}

If you want to call a function that receive as parameter you need to use call() method:
if (user!.emailVerified) {
await awaitingEmailConfPage.call();
} else {
await confirmedEmailConfPage.call();
}

Related

Flutter Riverpod Async Notifier | Unhandled Exception: Bad state: Future already completed

I am using the riverpod_generator, and the AsyncNotifier in riverpod
part 'auth_controller.g.dart';
// 3. annotate
#riverpod
// 4. extend like this
class AuthController extends _$AuthController {
// 5. override the [build] method to return a [FutureOr]
#override
FutureOr<void> build() {
// 6. return a value (or do nothing if the return type is void)
}
Future<void> signIn({
required String email,
required String password,
}) async {
final authRepository = ref.read(authRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(
() async => await authRepository.signIn(email, password));
}
Future<void> logout() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await FirebaseAuth.instance.signOut();
});
}
}
So when I am trying to logout the error will be occur it said
Future already completed
How to solve this?
I solve this problem, actually I listen to the changes of FirebaseAuth currentUser, then it will automatically redirect either homepage (if logged in) or login page (if logout), therefore the error will be occurred because I subscribe to the changes of FirebaseAuth

Need to get state data from Authentication Cubit inside another Cubit. In essence, I need to read the state

Note: I've seen similar questions all over Stackoverflow, but none of them exactly answer this.
Context:
I have an authentication cubit that has a variety of user states:
/// User exists, includes user's access and refresh tokens.
class User extends AuthenticationState {
final Tokens? tokens;
final bool justRegistered;
final bool tokensAvailable;
User({
required this.tokens,
this.justRegistered = false,
this.tokensAvailable = true,
});
#override
List<Object?> get props => [tokens, justRegistered, tokensAvailable];
}
/// No authenticated user exists.
class NoUser extends AuthenticationState {}
/// User is currently being registered or signed in.
class UserLoading extends AuthenticationState {}
Problem:
I have a Posts Cubit that needs to manage API calls and pass in the current user's access token to them in order to work. However, this access token is only stored inside the Authentication Cubit's state (in the User state, inside the tokens variable).
Is there a (good practice) way to get this token from the Authentication Cubit's User state inside the Post Cubit so I can use it to send API requests that require tokens for authentication to return posts?
Thanks!
You can create a class something like API Provider and authenticateService, pass and use in your Cubit to call API :
For example:
Future<Response<dynamic>> _callAndRetryDelete(
String url, {
int validStatus,
int retries,
}) async =>
retry(
() async {
final Map<String, String> authHeaders = await _authenticationService.getAuthHeaders;
if (authHeaders == null) {
throw ApiError('Could not get authorization', endpoint: url, method: HTTPMethod.Delete);
}
_client.options = _options(success: validStatus, authHeaders: authHeaders);
try {
return await _client.delete(url);
} on DioError catch (error) {
final String message = _error(error);
throw ApiError(message, endpoint: url, method: HTTPMethod.Delete);
}
},
maxAttempts: retries,
);
AuthenticaionService.class
Future<void> _doLogin(Map<String, dynamic> params) async {
final Response<Map<String, dynamic>> response =
await _handler.post(_loginEndpoint, body: params,
validStatus: 200);
final Map<String, String> res =
response?.data?.map((String key, dynamic value) => MapEntry<String, String>(key, value.toString()));
final Authentication authentication = Authentication.parse(res);
await _parseAndStore(authentication);
return authentication;}
AuthCubit:
Future<void> signIn() async => cubitAction(
action: () async {
emit(AuthenticationLoading());
await _authenticationService.signIn(payload);
emit(AuthenticationDone);
}
);

After first login red null error comes just for a second

When i run the app and log-in / or already logged in before closed app => "Null check operator used on a null value" error pops up and it goes away just in a second. I guess user going to page before data came up.
If i sign-out and log in with another acc. Red screen wont came up but the last users data appears just for a second then current users data comes after. Could you please help me about this.
My primitiv Sign-in method
Future signIn() async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
print('Signed In');
} on FirebaseAuthException catch (e) {
print (e);
Utils.showSnackBar(e.message);
}}}
User Provider
class UserProvider with ChangeNotifier {
User? _user;
final AuthMethods _authMethods = AuthMethods();
User get getUser => _user!;
Future<void> refreshUser() async {
User user = await _authMethods.getUserDetails();
_user = user;
notifyListeners();}}
The code i use refresh User welcomepage level
class _welcomePageState extends State<welcomePage> {
void initState() {
super.initState();
addData();
}
addData() async{
UserProvider _userProvider = Provider.of(context,listen: false);
await _userProvider.refreshUser();
}
#override
if that codes not enough to solve i can share all could you please help me to solve this problem.
You are force-unwrapping here :
User get getUser => _user!;
which means when you hot-reload the app it is null and the crash appears until it is null, you should make it optional and provide a default value so it doesn't crash.

show display name after signUp

I have a flutter firebase createUserWithEmailAndPassword function with the displayName update.
display name prints normally at the moment of fb user creation.
After signup MainPage loads with the users email and displayName. But displayName returns null value error. If I delete displayName from the MainPage - all works fine.
If I reload app, it works fine.
When I login, it works fine.
It can't pass the displayName at the moment of signup only.
Where I am wrong?
class AuthServiceProvider extends ChangeNotifier {
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final googleSingIn = GoogleSignIn();
UserModel? _userFromFirebase(auth.User? user) {
if (user == null) {
return null;
}
return UserModel(
user.displayName,
user.uid,
user.email,
);
}
Stream<UserModel?>? get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebase);
}
Future<UserModel?> createUserWithEmailAndPassword(
String name,
String email,
String password,
) async {
try {
final userCred = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
auth.User? firebaseUser = _firebaseAuth.currentUser;
if (firebaseUser != null) {
await firebaseUser.updateDisplayName(name);
await firebaseUser.reload();
firebaseUser = _firebaseAuth.currentUser;
}
print('FIREBASE USER IS $firebaseUser');
return _userFromFirebase(firebaseUser);
} catch (e) {
print(e.toString());
return null;
}
}
}
If your class were to extend either StatelessWidget or StatefulWidget, then all you'd have to do is to pass the data (displayName) between the screens.
This is not an answer but a suggestion:
You should try changing the ChangeNotifier to a StatefulWidget
and pass the data between screens...
You could also setup an
Authentication class that will hold all these Future methods so that
these calls can be reusable in your code. With this method, all you have to do is to call the specific function and give its required parameters.
As usually the solution is very simple if you think a little bit.
As all this is through the firebase auth, at the main page loading I just grab the firebase user with its display name that is saved in FB both for GoogleSignIn and createUserWithEmailAndPassword (required at registration)
import 'package:firebase_auth/firebase_auth.dart' as auth;
final auth.FirebaseAuth _firebaseAuth = auth.FirebaseAuth.instance;
final String firebaseUser =
_firebaseAuth.currentUser!.displayName ?? 'Unknown user';

Updating State with Provider after API call

How do I update the state with Provider after I've called the API to update it to the BE? I pass the arguments from one screen to another, then after I edit the text, I trigger the API call to the BE and I get the return value of the response and I want to update the state with that response with Provider. How is that possible? Here is the code:
Here I call the API and pass the arguments I've edited in my text fields:
onPressed: () async {
final updatedUser = await await APICall.updateUser(
userID,
updateName,
updateEmail,
);
Provider.of<UserStore>(context, listen: false)
.updateUser(updatedUser);
Navigator.pop(context);
},
Here is the API call where I return the response of the updated User:
Future<User> updateUser(String userID, String name, String email) async {
final response =
await APICalls.apiRequest(Method.PATCH, '/users', this._jsonWebToken,
body: jsonEncode({
"id": userID,
"name": name,
"email": email,
}));
Map<String, dynamic> jsonDecodedResponse = jsonDecode(response.body);
return User(
id: jsonDecodedResponse['data']['id'],
name: jsonDecodedResponse['data']['name'],
email: jsonDecodedResponse['data']['email'],
);
}
Now I wanted to pass that response I've got from the API call to pass it to the providers state:
deleteUser(User list) {
_userList.remove(list);
notifyListeners();
}
addUser(User list) {
_userList.add(list);
notifyListeners();
}
updateUser(User ){//I'm not sure how do define the updateUser method...
notifyListeners();
}
The update works on the BE side, and on the FE only when I refresh the widget, not immediately after the response is returned, which is the way I want it to work.
class UserStore extends ChangeNotifier {
List<User> clientList = [];
void deleteUser(User list) {
clientList.remove(list);
notifyListeners();
}
void addClient(User list) {
clientList.add(list);
notifyListeners();
}
void updateUser(User user){
clientList[clientList.indexWhere((element) => element.id == user.id)] = user;
notifyListeners();
}
}
What you have to do now is to listen to this provider on your widget. When the user will be updated, the changes will be applied to any widget listening to the clientList.
Note I've changed the clientList variable to public, so it can be listened by any widget outside.