Keep the user logged in flutter (The app has 2 different login and main, one for Client and one for Driver) - flutter

I am doing an app in flutter and I am working on the authentication part. I want to know how I can keep my user logged in after I reload the app. Now the thing is that my app has 2 kinds of users (Client and Driver). So each has its own space, like sign in and sign up and main (after logging in).
This is the code that I used for logging.
class Initializer extends StatefulWidget {
// Access to this Screen
static String id = 'initializer';
#override
_InitializerState createState() => _InitializerState();
}
class _InitializerState extends State<Initializer> {
// Firebase Stuff
final _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
User _user;
// To Check if There's a Driver
bool isDriver = true;
void getCurrentUser() async {
try {
final getCurrentUser = _auth.currentUser;
if (getCurrentUser != null) {
getUserKind();
_user = getCurrentUser;
}
} catch (e) {
print(e);
}
}
getUserKind() async {
try {
// To fetch Database for Driver
final QuerySnapshot checkOfDriver =
await _firestore.collection('driver').where('uid', isEqualTo: _user.uid).get().catchError((error) {
print(error);
});
if (checkOfDriver.docs.isEmpty)
setState(() {
isDriver = false;
});
else
setState(() {
isDriver = true;
});
} catch (e) {
print(e);
return null;
}
}
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
#override
void initState() {
super.initState();
getCurrentUser();
}
#override
Widget build(BuildContext context) {
getCurrentUser();
SizeConfig().init(context);
return _user == null
? WelcomeScreen()
: isDriver
? DriverMain()
: ClientMain();
}
}
It's actually working but not properly, because when I reload the app while I'm logging in as a Client, the app shows me DriverMain at the beginning for one second then it switches to the right side which is ClientMain and that causes me some errors sometimes, and it's not an efficient work anyway.
So, what I should add to the code or ...

Firebase already persists the users credentials, and restores them automatically when the app restarts.
But this is an asynchronous process, as it requires a call to the server. By the time your getCurrentUser = _auth.currentUser code runs, that asynchronous process hasn't finished yet, so you get null.
To properly respond to the auth state being restored (and other changes), you'll want to use an auth state change listener as shown in the documentation on authentication state:
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
If you want to use this in your UI, you'll typically wrap it in a StreamBuilder instead of calling listen yourself.

Related

Flutter uni_links duplicate the app every time a link is clicked

I am implementing a password recovery function based on the url sent to the email. Opening the app based on that url was successful. But instead of directly opening the required page in the app that is in the background, it duplicates the app. Although it still leads me to the password recovery page, now there will be 2 same apps running side by side
Procedure
Enter your email to send the password reset link
Click submit
Open the email containing the recovery link
Duplicate the app and open a recovery password page
Things what happen
Splash screen, first page open in the app, I am trying to do as instructed from uni_links package but still no success. Currently the function getInitialLink has the effect of opening the app based on the recovery link
class SplashController extends GetxController {
final SharedPreferencesHelper _helper = Get.find<SharedPreferencesHelper>();
late StreamSubscription sub;
#override
void onReady() async {
super.onReady();
await checkToken();
}
Future<void> checkToken() async {
await Future.delayed(Duration(seconds: 3));
var token = _helper.getToken();
if (token == null) {
Get.offNamed(Routes.LOGIN);
} else {
Get.offNamed(Routes.MAIN);
}
}
#override
void onInit() {
super.onInit();
initUniLinks();
}
Future<Null> initUniLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String? initialLink = await getInitialLink();
if (initialLink != null) {
print("okay man");
Get.toNamed(Routes.RECOVERY);
}
sub = getLinksStream().listen((link) {
}, onError: (err) {
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
}
}
}
I found the solution, actually this answer is already on Stackoverflow, and it's really simple.
In the AndroidManifest.xml file of the app. Find "android:launchMode" and change its old value to singleTask. And here is the result
android:launchMode="singleTask"

How to throw error inside riverpod future provider and catch it on error flutter

final loginProvider =
FutureProvider.family<bool, LoginParam>((ref, param) async {
if (param.sgId == '' || param.password == '') {
return false;
}
final http.Response response =
await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity =
await ref.watch(userDefaultsProvider(param.sgId).future);
//ref.state = AsyncValue.data(true);
return true;
} else {
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
});
void login(String userName, String password) async {
state = AsyncValue.loading();
AsyncValue<bool> result;
try {
result = await ref.refresh(loginProvider(LoginParam(userName, password)));
state = result;
} catch (e) {
state = AsyncError(e);
}
}
I'm trying to throw an custom exception inside riverpod future provider and catch the exception in other state notifier classes, but the catch block is not triggered.
Is there any other way to handle exceptions that future provider throw.
First of all, you won't have to manually catch errors inside a FutureProvider, it will do that for you. Refer this example.
Generally, the operations that happen after certain "user interaction" like a button click (in this case, login operation), are not meant to be written in FutureProvider. Scenarios where you'd be using FutureProvider are as follows:
Fetching some data over HTTP/HTTPS.
Performing operations like reading a file or a local database.
So your use case of login can be achieved using a StateNotifier.
// auth_provider.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
// Always prefer some strongly typed object to
// know current status of authentication.
enum AuthState {
unauthenticated,
authenticated,
authenticating,
failed,
}
// StateNotifier is recommended to encapsulate all your business
// logic into a single class and use it from there.
class AuthStateNotifier extends StateNotifier<AuthState> {
// Initialize with the default state of "unauthenticated".
const AuthStateNotifier() : super(AuthState.unauthenticated);
Future<void> login(LoginParam params) async {
if (param.sgId.isEmpty || param.password.isEmpty) {
state = AuthState.failed;
return;
}
final http.Response response = await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity = await ref.watch(userDefaultsProvider(param.sgId).future);
state = AuthState.authenticated;
return;
} else {
state = AuthState.failed;
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
}
}
// Finally, create a provider that can be consumed in the presentation layer (UI).
final authProvider = StateNotifierProvider<AuthStateNotifier, AuthState>((ref) => const AuthStateNotifier());
Then, in your UI part, usually in the onTap / onPressed event handler of button, you can use it as follows. Please note that, we have created a button widget that extends the ConsumerWidget to access the ref.
// login.dart
import 'auth_provider.dart';
class LoginButton extends ConsumerWidget {
final LoginParam params;
const LoginButton({
Key? key,
required this.params,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
void login() {
try {
await ref.read(authProvider.notifier).login(params);
} catch (e) {
// Handle error here.
}
}
return ElevatedButton(
child: Text('Login'),
// Call the handler here.
onPressed: login,
);
}
}

is it possible to use await in a sync function in dart

I have a function to get the user login status, now I want to add a logic with silent login when checked user did not login.After the user log, I store the login information about user name and password into local secure storage after the user login for the first time, when invoke the islogin function, my code looks like this:
bool get isLogin {
if(this == null){
// not login, do the automatic login logic
final UserAccount userAccount = UserAccount();
final String? phone = await SecureStorageUtil.getString("username");
final String? password = await SecureStorageUtil.getString("password");
if(phone == null || password == null){
return false;
}
final Result<Map> result = await userAccount.login(phone, password);
if(result != null){
return true;
}
}
return this != null;
}
what make me stuck is that the fetch credential information and login was async and have to using await keywords to wait the return. But the await keyword only allow in async function.
The await expression can only be used in an async function.
If I change the isLogin function to async, many places in this project must change. I was wonder is it possible to using await in the sync function? so I could do the auto login if the user logined for only one time. And did not do any change with the previous code.
Try to replace you function signature with
Future<bool> get isLogin async {
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async {
await this.getUserVerificationInfo();
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
getUserVerificationInfo() async {
await someAsyncFunc();
timer.cancle();
}

How to close Functions in ChangeNotifier Provider Flutter

How to close a Function without disposing it. I needy this answer because when I log out, I need to close the functions in ChangeNotifier Class.
This Is my ChangeNotifier Class:
class ChatAndRequestProvider extends ChangeNotifier {
bool _areThereNewChatsAndRequests = false;
bool get areThereNewChatsAndRequests => _areThereNewChatsAndRequests;
set areThereNewChatsAndRequests(bool value) {
_areThereNewChatsAndRequests = value;
notifyListeners();
}
List _chatsList = [];
List get chatsList => _chatsList;
set chatsList(List list) {
_chatsList = list;
notifyListeners();
}
getChats() async {
var prefs = await SharedPreferences.getInstance();
print('The getChats Id is ${prefs.getString(kUserId)}');
FirebaseDatabase.instance
.reference()
.child('users')
.child(prefs.getString(kUserId))
.child('friendsArray')
.onValue
.listen((snapshot) {
Map list = snapshot.snapshot.value;
print('map is $list');
var newItems = [];
if (list != null) {
list.forEach((key, value) {
newItems.add(value);
});
chatsList = newItems;
var globalArray = [];
for (var item in newItems) {
if (item[kLastTimestamp] != item[kLastTimestampSeen]) {
areThereNewChatsAndRequests = true;
}
var status;
switch (item['friendsStatus']) {
case 'friends':
status = RequestStatus.alreadyAFriend;
break;
case 'notFriends':
status = RequestStatus.noRequest;
break;
case 'blocked':
status = RequestStatus.userThatBlockedMe;
break;
case 'unblocked':
status = RequestStatus.noRequest;
break;
}
globalArray.add({kUserId: item[kUserId], kTypeOfRequest: status});
}
valuesList = globalArray;
} else {
deleteFromList(null, RequestStatus.alreadyAFriend);
chatsList = [
{kUserId: 'null'}
];
}
});
}
So for example when I log In as user1 and I call this function in the LoadingScreen() I get all of users that are my friends, and I can go to the chats screen List and chat with my friends. Up to this point there is no issue. But when I log out and when I log in with another account lets say user2 and I call this function again, then I get error and two responses because I am calling this function twice. I am not using Auth Packet, I have my own database on MongoDB where I store user Info, but requests and chats are stored on RealTime Database.
So my Question is:
When user1 logs out of my app, I can not call dispose() on provider because if he wants to log in again to another account, he will get an error because Provider was disposed, so how can I stop listening to my database when user logs out and call this function again. Thank You very Much!!
I´m not sure if this works because I don't fully understand the flow of your app but you say that
I can not call dispose() on provider because if he wants to log in
again to another account,
when the users logs out shouldn't the app return to the first screen disposing the provider? (unless you create it in the MaterialApp, I'm not sure about that either). You could save the instance of the Firebase listener and then close it when you log out/ dispose the provider
var _myListener;
getChats() async {
var prefs = await SharedPreferences.getInstance();
print('The getChats Id is ${prefs.getString(kUserId)}');
_myListener = FirebaseDatabase.instance
.reference()
.child('users')
.child(prefs.getString(kUserId))
.child('friendsArray')
.onValue
.listen((snapshot) ...
....
/// The rest of your code
}
void closeListener(){ //call it when the user logs out
_myListener?.close();
}
#override
void dispose(){
closeListener(); // or call it in the dispose if you want
//to dispose and create a new provider when the user logs out/ sign in
super.dispose();
}

Flutter throws error when context is passed to a Provider from the init method

I have a MainScreen (stateful) with the following method:
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) async {
loadFirebaseUser(context);
});
}
My 'loadFirebaseUser' method is in another file called Constants.dart which can be used from any screen.
The function is as follows:
Future<bool> loadFirebaseUser(BuildContext context) async {
Auth _auth = Auth();
FirebaseUser cUser = await _auth.currentUser();
if (cUser.isEmailVerified) {
DocumentSnapshot snapshot = await Firestore.instance
.collection('Profile')
.document(cUser.uid)
.get();
if (snapshot.data != null) {
User user = Provider.of<UserData>(context).getUser();
user = User.fromMap(snapshot);
Provider.of<UserData>(context).setUser(user);
return true;
} else {
return false;
}
}
return false;
}
I am getting the follwing error when this code is executed:
Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
Needless to say that as result of this the user is not getting loaded. The error points to the following line:
User user = Provider.of<UserData>(context).getUser();
I want the 'loadFirebaseUSer' function to execute automatically and not on any button click, so this is the only place I know to place this code. Is there a way to achieve this differently? Thanks
Did you tried to get context this way?
Future.delayed(Duration.zero, () {
var myState = Provider.of<MyState>(context);
.......
});