Flutter Authentication signOut - uid waas called null error - firebase

I'm logging out of the application with authentication, but I still get uid null error
This is my signOut method
Future<bool> signOut() async {
try {
await _auth.signOut();
_user = null;
return true;
} catch (e) {
return false;
}
}
This is my widget.I can log out successfully, but when switching to the next page it gives uid error and switches
actions: <Widget>[
PopupMenuButton<String>(
onSelected: (value) async {
switch (value) {
case 'Çıkış Yap':
//problem here
var provider = Provider.of<AuthServices>(context, listen: false);
await provider.signOut();
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => GirisEkrani()));
break;
case 'Profil Ayarları':
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfilGuncellemeEkrani()));
break;
}
},
itemBuilder: (BuildContext context) {
return {'Profil Ayarları', 'Çıkış Yap'}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
),
],
Error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid

It looks like you're using the user's uid in the GirisEkrani widget.
You should remove any reference to the uid in there since the user is already signed out and you've set the _user variable in your provider to null.

Related

Another exception was thrown: Null check operator used on a null value!!1

I tried to change the password in app with flutter (Firebase) but it give me and error Another exception was thrown: Null check operator used on a null value. so can someone help me fix this one.
do I need to add something to check null or remove something.
class ChangingPasswordScreen extends StatefulWidget {
static const routeName = '/ChangingPasswordScreen';
//late final String? email;
//final User user;
//ChangingPasswordScreen({required this.user});
#override
State<StatefulWidget> createState() {
return _ChangingPasswordState();
}
}
class _ChangingPasswordState extends State<ChangingPasswordScreen> {
final _formKey = GlobalKey<FormState>();
var newPassword = " ";
final newPasswordController = TextEditingController();
#override
void dispose() {
newPasswordController.dispose();
super.dispose();
}
final currentUser = FirebaseAuth.instance.currentUser;
changePassword() async {
try {
await currentUser!.updatePassword(newPassword);
FirebaseAuth.instance.signOut();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SigninScreen(),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Your Password has been changed'),
),
);
} catch (error) {}
}
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
setState(() {
newPassword = newPasswordController.text;
});
changePassword();
}
},
child: Text(
'Change Password',
style: TextStyle(fontSize: 18.0),
),
),
In,
await currentUser!.updatePassword(newPassword);
make sure that currentUser is not null before using !. ! indicate that the variable will never be null.
Try something like this,
if(currentUser != null){
try {
await currentUser!.updatePassword(newPassword);
FirebaseAuth.instance.signOut();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SigninScreen(),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Your Password has been changed'),
),
);
} catch (error) {
print("Error thrown : $error");
}
}
Note: Never leave a catch block empty. Log the error while debugging. Handle the error properly. If you leave a catch block empty, you'll never know if it throws an Exception or not.

Some Problems In Flutter Firebase Login

I tried coding a login and registration form in an app with firebase auth. There are some problems in my login from.
Please look at this loginForm function which will execute when login button is pressed.
Future loginForm() async {
FormState formSate = _formKey.currentState;
if (formSate.validate()) {
final User firebaseUser = (await firebaseAuth
.signInWithEmailAndPassword(
email: _emailcontroller.text,
password: _passwordcontroller.text)
.catchError((errMsg) {
displayToast("Error: " + errMsg.toString(), context);
}))
.user;
if (firebaseUser != null) {
setState(() {
loading = true;
});
usersRef.child(firebaseUser.uid).once().then((DataSnapshot snap) async {
if (snap.value != null) {
SharedPreferences preferences =
await SharedPreferences.getInstance();
preferences.setString("email", _emailcontroller.text);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return LocationHome();
}));
displayToast("Succesfully LoggedIn!", context);
} else {
firebaseAuth.signOut();
displayToast("No user found! Please try SignUp", context);
}
});
} else {
displayToast("Error Occured! Cannot log you in", context);
}
}
}
}
You can see here that after login I have programmed it to navigate to Location Page.
But to make user stay logged in I have used a StreamBuilder and checking if snapshot.hasdata in the main file
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
title: 'TaakStore',
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
print(snapshot);
return Home();
} else {
return Login();
}
},
),
));
}
In this, you can see that if snapshot.hasdata it should navigate to home screen and if not data then nav to the login screen. The first time when a user opens the app the snapshot has no data so it will open a login screen which is perfect. But the problem is when the user clicks on login button instead of going to location screen it is directly going to home screen because the snapshot has data which is ridiculous.
If someone understand my problem please help me
I think the problem is occuring by using the streamBuilder as streamBuilder continously keeps looking for stream or data and as soon it found the appropriate data it performs the assigned function which is navigating the user to the homeScreen() instead of LocationScreen()
Repleace StreamBuilder on the Main() with the bellow code:
if (FirebaseAuth.instance.currentUser != null) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Home(),
),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => Location(
),
),
);
}
This will not keep on looking for the stream and only execute the function once when the app is restarted. The same method have been suggested by FirebaseFlutter .

UID call on null and provider failing to pass on data with TypeError (type 'FirebaseUser' is not a subtype of type 'String')

I tried to reach user data to determine Admin role and pass on to Future Builder. Depending on Admin, the result will be determine if which widget will be available.
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
floatingActionButton:
FutureBuilder(
future: _getProfileData(user, authNotifier),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
_isAdmin = snapshot.data['isAdmin'] ?? false;
}
return Container(
child: Column(
children: <Widget>[
adminFeature(),
]
));
}),
The widget below provides information on user.
Widget adminFeature() {
if(_isAdmin == true) {
return
FloatingActionButton(
backgroundColor:CompanyColors.blue[500],
child: const Icon(Icons.add),onPressed: () {
var foodNotifier = Provider.of<FoodNotifier>(context, listen: false);
foodNotifier.currentFood = null;
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context) {
return FoodForm(
isUpdating: false,
);
}),
);
});
} else {
return Container();
}
}
_getProfileData(User user, AuthNotifier authNotifier) async {
final uid = await Provider.of<AuthNotifier>(context, listen: false).getCurrentUser();
await Firestore.instance
.collection('Users')
.document(uid)
.get().then((result) {
user.isAdmin = result.data['isAdmin'];
});
}
Below, this is the provider. It works for all the apps, but I fail to get the user data and pass on to a widget.
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => AuthNotifier(),
),
ChangeNotifierProvider(
create: (context) => FoodNotifier(),
),
ChangeNotifierProvider(create: (context) => ThemeProvider(isLightTheme: false)),
ChangeNotifierProvider(create: (context) => UserxProvider()),
ChangeNotifierProvider(create: (context) => UsersProvider()),
],
child: MyApp());
}
Notifier is mentionned below:
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Future<String> getCurrentUID() async {
return (await _firebaseAuth.currentUser()).uid;
}
// GET CURRENT USER
Future getCurrentUser() async {
return await _firebaseAuth.currentUser();
}
In your _getProfileData method, you should change the following:
await Provider.of(context, listen: false)
to
await Provider.of<UserxProvider>(context, listen: false)
// or UsersProvider, whichever you're trying to call
The problem is that when you call Provider without specifying the model that you want (i.e., the type), it becomes Provider.of<dynamic> and hence you're getting that error message.

How to maintain Firebase Authentication after refresh with Flutter web?

I am using the authStateChanges stream from Firebase with flutter. I have two views, one for mobile and the another one for a web application. I want to redirect the user to the SignIn screen if he is not connected, logged in or authenticated. At first it works well but then when i am logged in and refresh the browser i got the SignIn screen loaded for like 1 second and then the Web screen appears again. I checked with print what's going on and from what i saw, the authStateChanges Stream is null for that 1-2 seconds(when SignIn screen appears) and then has a value when the stream receives the connected user. Is there a way to check, or wait until this authentication is done before loading the SignIn screen when it must not load it ?
My main component contains the StreamBuilder as following:
Widget build(BuildContext context) {
final firebaseAuthService = Provider.of<FirebaseAuthService>(context);
return StreamBuilder<User>(
stream: firebaseAuthService.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null) {
//first time no connection
return SignIn();
}
if (kIsWeb) {
return WebMain(user: user);
}
// load mobile version
return MobileMain();
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
});
}
Here you can find my FirebaseAuth wrapper class which contains the methods from firebase:
class FirebaseAuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
User _user;
bool get isAuthenticated {
return _user == null ? false : true;
}
User get user {
return _user;
}
Future<User> signInWithEmailAndPassword(
String userEmail, String userPassword) async {
return _user = await _firebaseAuth
.signInWithEmailAndPassword(email: userEmail, password: userPassword)
.then((userCredential) => userCredential.user);
}
Stream<User> authStateChanges() {
_user = _firebaseAuth.currentUser;
return _firebaseAuth.authStateChanges();
}
Future<void> signOut() async {
return _firebaseAuth.signOut();
}
}
While I am not sure why authStateChanges does not notify when the user sign in state is changed (usually a second later), a similar function does seem to work for your use case.
Try idTokenChanges()
FirebaseAuth.instance.idTokenChanges().listen((event) {
print("On Data: ${event}");
});
This event will return your Firebase User object. When refreshed, it might return 'null' initially, but within a second, returns your signed in User. You could potentially make the sign in page wait a couple of seconds to make sure a signed in user isn't being initialized.
EDIT:
While there may be better solutions, this is currently working for me.
final subscription = FirebaseAuth.instance.idTokenChanges().listen(null);
subscription.onData((event) async {
if(event != null) {
print("We have a user now");
isLoading = false;
print(FirebaseAuth.instance.currentUser);
subscription.cancel();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => OverviewController())
);
} else {
print("No user yet..");
await Future.delayed(Duration(seconds: 2));
if(isLoading) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginController())
);
isLoading = false;
subscription.cancel();
}
}
});
For me, the below code seems to work fine. Although there is a warning in docs that says "You should not use this getter to determine the user's current state, instead use [authStateChanges], [idTokenChanges] or [userChanges] to subscribe to updates."
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Diary Book',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
primarySwatch: Colors.green,
),
home: (FirebaseAuth.instance.currentUser == null)
? LoginPage()
: MainPage(),
);
}
}
I haven't encountered any issues using the above code. I Will let you know if do. If someone can comment any future errors this may have that would be great
FirebaseAuth.instance.authStateChanges().listen(
(event) {
if (event == null) {
print('----user is currently signed out');
} else {
print('----user is signed in ');
}
runApp(
const MyApp()
);
},
);

How to call a function ASA the app runs in flutter?

I have an application that do login with the help of firestore database and I want to do autologin so I made a boolean and set it to false in the database and made the login function set it to true as he or she sign in, so I want to check if the person have already signed in or not as the app runs, any ideas :) ?
here my code:
void getUserData() async {
try {
var firebaseUser = await FirebaseAuth.instance.currentUser();
firestoreInstance
.collection("Students")
.document(usernameController.text)
.get()
.then((value) {
setState(() {
email = (value.data)['email'];
password = (value.data)['password'];
gender = (value.data)['gender'];
loggedin = (value.data)['token'];
});
});
} catch (e) {
print(e.toString);
}
}
You dont have to use a boolean to check if the user is logged in or not. Firebase authentication already offers that. You can check inside the initState:
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then((res) {
print(res);
if (res != null) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => Home(uid: res.uid)),
);
}
else
{
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SignUp()),
);
}
});
}
Checks if there is a current user or not and navigates to the required page.
If you have different types of users, then you have to identify them in the database. So authenticate in firebase authentication, and use a userType field in the database:
void registerToFb() {
firebaseAuth
.createUserWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((result) {
firestoreInstance.collection("users").document(result.user.uid).setData({
"email": emailController.text,
"name": nameController.text,
"userType" : "Students"
}).then((res) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => Home(uid: result.user.uid)),
);
});
}).catchError((err) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text(err.message),
actions: [
FlatButton(
child: Text("Ok"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
});
}

Resources