Flutter Firebase delete user account - flutter

I'm attempting to delete a Firebase user account that has data in several other collections. Because Firebase requires a recent login in order to delete accounts or change passwords, I'm asking the user to sign in again in the following code. The code below deletes the user account, but the application automatically shuts after pressing the delete button. Lost connection to the device. The user is still signed in with their data when I restart the app (perhaps because it is persistent), so I have to manually log them out.
The code is as follows-
This Function runs when delete account button is pressed.
/// Function to delete user account
void deleteUserAccountEmail(
{required String password, required BuildContext context}) async {
//
try {
await _auth
.signInWithEmailAndPassword(
email: currentUser!.email!, password: password)
.then(
(_) async {
//
currentUser = _auth.currentUser;
//
_rankController.deleteRank();
// Deleting user
currentUser!.delete().then(
(value) => signOut(),
);
},
);
} catch (error) {
final e = error.toString().replaceRange(0, 14, '').split(']')[1];
_uniWidgets.errorSnackBarFN(
errorTitle: 'Error',
errorMessage: e,
);
}
}
StreamBuilder in the HomeScreen Body
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
return _authController.currentUser!.emailVerified
? HomeScreen()
: AuthScreen();
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return AuthScreen();
}
},
);
} else {
return NoConnectionScreen();
}

Related

Why is Firebase Auth userChanges() not called when Display Name is updated - Flutter

My app has and AuthGate() that either sends a user to the SignInScreen(), the UpdatesNeededScreen(), or the FloatingTabBarView(), depending on the current users data. If the email, pictureURL, or displayName are null, the UpdatesNeededScreen() is shown and there's a ListView in that screen showing which info needs to be added. When a user adds their email or name I also use user.reload() at the end of those functions just to make sure the AuthGate() reloads the user data and the user is sent back to the UpdatesNeededScreen() if more info is missing or to the FloatingTabBarView() if that was the last thing needed. This works perfectly with the email but when the name is added the AuthGate() never updates. If I close the app after that and reopen it, the new display name is there, but that's not being recognized when it's actually updated. Does anyone know why?
AuthGate():
class AuthGate extends StatelessWidget {
const AuthGate({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(stream: FirebaseAuth.instance.userChanges(), initialData: FirebaseAuth.instance.currentUser, builder: (context, snapshot) {
// User is not signed in
if (!snapshot.hasData) {
if (kDebugMode) {
print('sign in');
}
return const SignInScreen();
}
final User user = snapshot.data!;
if (user.email == null || user.photoURL == null || user.displayName == null) {
// Show edit profile screen
if (kDebugMode) {
print('edit profile');
}
List infoNeeded = [];
if (user.email == null) {
infoNeeded.add('email');
}
if (user.photoURL == null) {
infoNeeded.add('photoURL');
}
if (user.displayName == null) {
infoNeeded.add('displayName');
}
return UpdatesNeededScreen(infoNeeded: infoNeeded);
}
// All good
if (kDebugMode) {
print('signed in AND all info given');
}
return const FloatingTabBarView();
},
);
}
}
Function to save the display name:
void saveChanges() {
if (_nameFormKey.currentState!.validate()) {
_nameFormKey.currentState!.save();
setState(() {
_isLoading = true;
});
User user = FirebaseAuth.instance.currentUser!;
try {
user.updateDisplayName(_nameController.text).then((value) {
FirebaseFirestore.instance.collection('users').doc(user.uid).set(
{'displayName': _nameController.text},
SetOptions(merge: true)).then((nn) {
setState(() {
_isLoading = false;
});
user.reload();
});
});
} on FirebaseAuthException catch (e) {
if (kDebugMode) {
print(e.message);
}
}
}
}
I found the issue. If I slid back to the UpdatesNeededScreen() it was correct and didn't ask for the name anymore. The StreamBuilder() was updating but since I was pushing different screens to update info, I needed to put Navigator.of(context).pop() at the end of the function.

Flutter How to check whether the user sign up with google provider before?

I want to check whether the user has signed up before or not
(if this is his first time I will send him to write some data if not he will enter to the main page of the app).
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
Future googlelogin() async {
try {
final googleUser = await googleSignIn.signIn();
if (googleUser == null) return;
_user = googleUser;
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential);
} catch (e) {
print(e.toString());
}
notifyListeners();
}
I also have a StreamBuilder to check the data that user enter
StreamBuilder<Object>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator.adaptive(),
);
} else if (snapshot.hasData) {
return LoggedInWidget();
} else if (snapshot.hasError) {
return Center(
child: Text('Something went wrong'),
);
} else {
return SignUp();
}
})
You can use the pre-built method "isNewUser" of the "UserCredential" class. Call the sing in with credentials function from a variable and use said variable to perform the check.
...
var result =
await FirebaseAuth.instance.signInWithCredential(credential);
if (result.additionalUserInfo!.isNewUser) {
// Perform what you need to do for new users here
// like creating a user document
}else {
//Perform what you want to do for old users here
//like fetching a specific user document
}
...

Flutter snapshot.hasError is always false

I'm pretty new to flutter. I can't understand why in my code I cannot trigger the snapshot.hasError property. Here is my code, it's basically the same as in the Flutter examples:
Inside the column builder:
...
RoundedButton(
text: "SIGNUP",
press: () {
setState(() {
_signupRequested = Auth().signup(_emailController.text,
_passwordController.text, _usernameController.text);
});
},
),
...
The signup function:
class Auth extends BaseLogic {
Future<bool> signup(String email, String password, String username) async {
BasicResponse response = await AuthRepo().signup(email, password, username);
if (response.success) {
await this.setValue('signup_requested', 'y');
return true;
} else {
throw Exception(response.reason!);
}
}
...
Future builder:
FutureBuilder<bool> buildFutureBuilder() {
return FutureBuilder<bool>(
future: _signupRequested,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {
return buildWait();
} else {
return buildColumn(null);
}
} else if (snapshot.hasError) {
return buildColumn(snapshot.error.toString());
}
return const CircularProgressIndicator();
},
);
}
If signup function throws the explicit exception or another exception (from the repo) the snapshot.hasError property is always false...
Ok I found the problem, I was using setState inside the builder to initialize the variable, somehow removing it made it work

Flutter Session stores data but can't make decisions based off it

I'm using FlutterSession to store phone number/email (based on user's choice) as a method of logging into the app. The session works fine in case of email but when I store and retrieve phone number, it shows me the snapshot has no data.
This is the method where I'm setting the session value for the phone number:
void phoneSignIn() async {
if (phoneNumber == null || phoneNumber.length < 10) {
print('Please enter a valid phone number!');
}
else {
await fillers.session.set("phoneNum", phoneNumber);
print('Phone number is: '+ phoneNumber);
setState(() {
phoneSignedIn = true;
});
var sessionCheck = await fillers.session.get("phoneNum");
print("Session value: $sessionCheck");
print(phoneSignedIn);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => OnboardOne()));
}
}
In the above code, I've made sure using print statements that the session is storing phone number value and also that the phoneSignedIn bool is set to true.
This is the method where I'm setting session value for email:
void signIn() async {
response = await Dio().post(url,
data: {
"email": emailVal,
"password": passwordVal
}
);
if (response.statusCode == 200) {
await fillers.session.set('email', emailVal);
await fillers.session.set('password', passwordVal);
print('Sign In successful!');
print('Email: $emailVal');
print('Password: $passwordVal');
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => OnboardOne()));
}
else
{
print('Exited with statuscode ${response.statusCode}');
print('Email: $emailVal');
print('Password: $passwordVal');
}
}
This is my firstpage where I decide on which page to go based on whether the user has logged in or not:
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: phoneSignedIn ? session.get("phoneNum") : session.get('email'),
builder: (context, snapshot) {
return snapshot.hasData ? Dashboard() : SignUpIn();
}
),
);
}
}
As you can see, I've done nothing different in the email sign in (except using a backend api to auth the email). Ultimately, I'm using the same technique to store both the email and phone number. But when I've signed in using phone number and then reopen the app, the app goes to the SignUpIn() page, when it should actually go to the Dashboard() page.
Also, on the dashboard, there's a button "Sign Out", on pressing which, the session values are cleared.
What am I doing wrong?

Flutter: StreamBuilder Snapshot -- No Data

I am just learning Flutter and am trying to use a StreamBuilder to display a Login / Register page if the user is logged out, or a Profile page if the user is logged in. My code is below:
Auth Service:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthUser {
AuthUser({#required this.uid, #required this.email});
final String uid;
final String email;
}
abstract class AuthBase {
Future<AuthUser> currentUser();
Future<AuthUser> signIn({String email, String pw});
Future<AuthUser> registerUser({String email, String pw});
Stream<AuthUser> get onAuthStateChanged;
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
AuthUser _userFromFirebase(FirebaseUser user) {
if (user != null) {
return AuthUser(uid: user.uid, email: user.email);
} else {
return null;
}
}
#override
Stream<AuthUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
#override
Future<AuthUser> currentUser() async {
final user = await _firebaseAuth.currentUser();
return _userFromFirebase(user);
}
#override
Future<AuthUser> signIn({String email, String pw}) async {
final authResult = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<AuthUser> registerUser({String email, String pw}) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
StreamBuilder:
class WelcomeScreen extends StatelessWidget {
WelcomeScreen({#required this.auth});
static const String id = '/';
final AuthBase auth;
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
AuthUser user = snapshot.data;
if (user == null) {
return displayLoginOrRegPage(context);
} else {
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
It was my understanding the stream would begin emitting 'null' once it was initialized, and would continue doing so until it fired off an Auth state change...
But the snapshot continually reports "No Data" and thus my code is stuck on the CircularProgressIndicator.
BTW, if I display the log-in screen in place of the progress indicator, the code works. So I'm clearly not understanding the whole stream initialization process.
Can somebody kindly explain to me where I have gone wrong here? Thanks a million in advance.
As you mentioned, when stream initialises it emits null, but when the user is not logged in, it still emits null, which stream considers as no data i.e null that's the reason for the error.
You can use Streambuilder's connection state to differentiate between no user null and null after initialisation.
I hope following code helps you.
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
return displayLoginOrRegPage(context);
} else {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
Per #VirenVVarasadiya, it was definitely a case of checking the ConnectionState. Here is the final working code. Thank you!
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data != null) {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
} else {
return displayLoginOrRegPage(context);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
Try to change WelcomeScreen to state full Widget.