I´m using Firebase to register my users and require authentication to go to the home screen implementing the signInWithEmailAndPassword() method and an Intent.
The authentication is working fine, it successfully login my users, BUT the problem is that when the authentication is successful and the HomeActivity starts, the Activity keeps on recreating infinitely and the screen stays black.
I´ve tried many ways of doing the Intent to move from the MainActivity to the HomeActivity after the login but that infinite loop keeps happening.
The weird thing is, if you close the app while the screen is black, "Force Stop" and then open the app again, the app goes to the HomeActivity directly without a problem. The HomeActivity contains a FrameLayout with a Fragment.
I do have the correct Gradle implementation for Firebase.
This is how the MainActivity.java's OnStart() and attemptLogin() look like:
private void attemptLogin() {
String email = Objects.requireNonNull(mEditTextUsername.getText()).toString().trim();
String password = Objects.requireNonNull(mEditTextPassword.getText()).toString().trim();
if (email.isEmpty() || password.isEmpty()) {
mInputLayoutUsername.setError(getString(R.string.error_emptySpace));
mInputLayoutPassword.setError(getString(R.string.error_emptySpace));
return;
}
mProgressBar.setVisibility(View.VISIBLE);
mEnterButton.setVisibility(View.INVISIBLE);
// Use Auth to sign in with email & password
mAuth.signInWithEmailAndPassword(email, password).addOnSuccessListener(new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
startActivity(new Intent(MainActivity.this, HomeActivity.class));
finish();
}
});
}
#Override
protected void onStart() {
super.onStart();
mUser = mAuth.getCurrentUser();
if (mUser != null) {
Log.d(TAG, "onCreate: Going home because mUser is not null");
startActivity(new Intent(MainActivity.this, HomeActivity.class));
finish();
}
}
In the Logcat this lines of code repeat forever (Some of them I use them as debug logs, like tha one of the Home has been created and the Fragment)
D/HomeActivity: onCreate: Home has been created
D/HomeActivity: onCreate: Fragment placed correctly
V/FA: Activity resumed, time: 25049213
V/FA: Screen exposed for less than 1000 ms. Event not sent. time: 9
V/FA: Activity paused, time: 25049222
V/FA: onActivityCreated
Inside of the HomeActivity I don't use a recreate() at all.
Thanks for your answers. I hope you can help me make that the app goes directly from the MainActivity into the HomeActivity without it recreating forever
This works fine, it will automatically be redirected to HomeActivity after successful login. If already login then on App Start HomeActivity will start.
private void signIn(String mail, String password) {
mAuth.signInWithEmailAndPassword(mail,password).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
updateUI();
}
else {
showMessage(task.getException().getMessage());
}
}
});
}
private void updateUI() {
startActivity(HomeActivity);
finish();
}
private void showMessage(String text) {
Toast.makeText(getApplicationContext(),text,Toast.LENGTH_LONG).show();
}
#Override
protected void onStart() {
super.onStart();
FirebaseUser user = mAuth.getCurrentUser();
if(user != null) {
//user is already connected so we need to redirect him to home page
updateUI();
}
}
Related
I swear I searched a lot but in the end I'm surrendering, I'm going to ask. I'm making an app where there is only one Activity and everything is managed through Fragments, everything works perfectly, layout is well setup, but I have a problem with the signup.
I have 3 types of signup, mail and password, Facebook and Google.
Emailsignup method:
private void emailSignUp() {
String email = etEmail.getText().toString();
String password = etPassword.getText().toString();
if(email.isEmpty()||password.isEmpty()) {
Toast.makeText(getActivity(), "Empty fields are not allowed.",
Toast.LENGTH_SHORT).show();
return;
}
mAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success");
user = mAuth.getCurrentUser();
//Read DB Score
database = FirebaseDatabase.getInstance();
database.getReference(user.getUid())
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if(!dataSnapshot.exists()) database.getReference(user.getUid()).setValue(0);
updateUI(user);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "createUserWithEmail:failure", task.getException());
Toast.makeText(getActivity(), "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
}
});
}
The other 2 are the same; the methods are called from onActivityCreated trough the use of onClickListener, the method works correctly.
As you can see, everyone of those methods creates a DB value with the UID of the user just created, and it sets it up to 0. Everything works perfectly up to here.
There is an updateUI method which is responsible to move Fragment if user is not null, the next Fragment is a Dashboard and here I have a problem with the data fetch from the server:
every time I register a new user I get a null object reference when I call the db because the async creation of the db key in the precedent fragment has still not finished, if I restart the app, the dashboard fragment works perfectly because the value is there.
In the dashboard Fragment I'm fetching DB data from onStart.
I'm almost completely sure that what's wrong in my code is the placing of the button calls or the fetchData in the Dashboard Fragment, I tried understanding Fragment lifecycle but can't understand it if async tasks are called.
Any help?
P.S.: Every object or value is set up as private in the Fragment code.
As requested the error:
2019-11-25 20:16:04.403 3342-3342/com.d136.uselessrank E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.d136.uselessrank, PID: 3342
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.isEmpty()' on a null object reference
at com.d136.uselessrank.fragments.fragmentDashboard.getName(fragmentDashboard.java:142)
at com.d136.uselessrank.fragments.fragmentDashboard.onStart(fragmentDashboard.java:67)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2632)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:915)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
and Fragment Dashboard:
private void getName() {
if(currentUser.getDisplayName().isEmpty()) email.setText(currentUser.getEmail());
else email.setText(currentUser.getDisplayName());
}
that is called from the same FragmentDashboard:
#Override
public void onStart() {
super.onStart();
getName();
getProfileImage();
readScore();
}
currentUser it's null for sure, the problem is that when currentUser is called (OnActivityCreated) the fragment still has not finished populating that db value. How do I make it wait for it?
Solved thanks to #Frank van Puffelen, in Fragment Dashboard I implemented:
#Override
public void onStart() {
super.onStart();
FirebaseAuth.getInstance().addAuthStateListener(new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
mAuth = FirebaseAuth.getInstance();
currentUser = mAuth.getCurrentUser();
updateUI(currentUser);
}
});
}
and after a little debugging everything went smoothly.
When you create a new user, the user object in the AuthResult will be populated correctly. However, the FirebaseAuth.getInstance().getCurrentUser() is populated asynchronously.
Usually this is not a problem, but if you transition to a new activity/fragment, you can't rely on FirebaseAuth.getInstance().getCurrentUser() being populated yet.
You have two options here:
Put the value of authResult.getUser() in shared preferences, and use that in the next activity/fragment as the current user.
Attaching an auth state listener with FirebaseAuth.getInstance().addAuthStateListener(listener) in the next activity/fragment, and wait for the user to be initialized there.
FirebaseAuth.getInstance().addAuthStateListener(new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
// ...
}
});
When an email verified user logs in for the first time in my app everything works as expected (he gets to a welcome screen and then to the MainActivity) but when he logs out out and logs in again there is a null pointer exception. When a user logs in for the second time he should go straight to the MainActivity and not to the welcome screen which you see in the below code.
This was a known issue about a year ago so my question is how to resolve this issue? Has this bug been fixed and when yes what did I do wrong in my code?
The question I am referring is here :Firebase user returns null metadata for already signed up users
Here is the code I am using to check whether the user logs in for the first or second time:
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
auth = FirebaseAuth.getInstance();
FirebaseUser firebaseUser = auth.getCurrentUser();
if (firebaseUser != null && firebaseUser.isEmailVerified()) {
startActivity(new Intent(LoginEmailActivity.this, MainActivity.class));
finish();
}
setContentView(R.layout.activity_email_login);
...
login.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
...
progressBar.setVisibility(View.VISIBLE);
final FirebaseUserMetadata metadata = auth.getCurrentUser().getMetadata();
//authenticate user
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(LoginEmailActivity.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
progressBar.setVisibility(View.GONE);
if (!task.isSuccessful()) {
Toast.makeText(LoginEmailActivity.this, "Ups, es ist wohl etwas schief gelaufen. Bitte überprüfe deine Internetverbindung und versuche es erneut.", Toast.LENGTH_LONG).show();
} else {
if (auth.getCurrentUser().isEmailVerified() && !(metadata.getCreationTimestamp() == metadata.getLastSignInTimestamp())){
Intent intent = new Intent(LoginEmailActivity.this, MainActivity.class);
startActivity(intent);
finish();
} else if (auth.getCurrentUser().isEmailVerified() && (metadata.getCreationTimestamp() == metadata.getLastSignInTimestamp())){
firstLoginScreen();
finish();
}
else if (!auth.getCurrentUser().isEmailVerified()){
Toast.makeText(LoginEmailActivity.this, "Bitte verifiziere erst deine E-Mail Adresse mit dem Link, den wir dir geschickt haben.", Toast.LENGTH_SHORT).show();
}
}
}
});
}
});
...
Here is the error:
java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.firebase.auth.FirebaseUserMetadata com.google.firebase.auth.FirebaseUser.getMetadata()' on a null object reference
at com.example.android.guessit.LoginRegistration.LoginEmailActivity$1.onClick
Also the "getCreationTimestamp" says that it may produce a NullPointerException
The problem is where you call
final FirebaseUserMetadata metadata = auth.getCurrentUser().getMetadata()
Calling it before completing the user login, brings you to the NullPointer you are facing. Just move it inside the onComplete callback, where the current user has a value.
Also, just for code optimisation add a return statement when you switch to the MainActivity.
if (firebaseUser != null && firebaseUser.isEmailVerified()) {
startActivity(new Intent(LoginEmailActivity.this, MainActivity.class));
finish();
return;
}
You need a return in your if statement when you go to the MainActivity. Otherwise after that you set again setContentView(R.layout.activity_email_login).
What currently happens: when user logs out as an anonymous user using onBackPressed and case R.id.logoutMenuOption , their Firebase Auth and Firebase Database Uid gets removed correctly. Except, when a user swipe closes the app the onDestroy() method is called but their Firebase Auth and Firebase Database Uid still remains in the Database. I use the onDestroy() when the user swipe closes the app.
What I need to happen: when a user swipe closes the app, the onDestroy() and removeAnonymousVendorUser() methods are called to delete the anonymous users authentication as well as their User Uid from the Realtime Database.
What I have done so far: I have created the removeAnonymousVendorUser() method and placed it in the onDestroy() method. I also will show how it is used in my other logout methods, and in those methods it does work, it just does not work for the onDestroy()
#Override
protected void onDestroy() {
super.onDestroy();
//TODO: Figure out how to delete UID and user when they sign out
removeAnonymousVendorUser();
FirebaseAuth.getInstance().signOut();
finish();
}
Method that removes the anonymous user Firebase Auth data and Firebase Database data:
private void removeAnonymousVendorUser() {
if (FirebaseAuth.getInstance().getCurrentUser() != null) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.delete();
}
DatabaseReference vendorIdReference = FirebaseDatabase.getInstance().getReference("Vendor");
vendorIdReference.removeValue();
}
Logout Method when user clicks Logout menu option button:
//Logout Menu Option Button
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// If Logout Button is clicked within 2 Seconds log user out
case R.id.logoutMenuOption:
if (logoutAppPressAgain + 2000 > System.currentTimeMillis()) {
logoutToast.cancel();
removeAnonymousVendorUser();
FirebaseAuth.getInstance().signOut();
removeAnonymousVendorUser();
Intent logoutIntent = new Intent(VendorMapsActivity.this, WelcomeActivity.class);
startActivity(logoutIntent);
finish();
} else {
logoutToast = Toast.makeText(getBaseContext(), R.string.press_button_again_to_logout, Toast.LENGTH_SHORT);
logoutToast.show();
}
logoutAppPressAgain = System.currentTimeMillis();
}
return super.onOptionsItemSelected(item);
}
Log out method when user presses back twice:
public void onBackPressed() {
if (exitAppPressBack + 2000 > System.currentTimeMillis()) {
exitAppBackToast.cancel();
super.onBackPressed();
removeAnonymousVendorUser();
FirebaseAuth.getInstance().signOut();
Intent logoutIntent = new Intent(VendorMapsActivity.this,
WelcomeActivity.class);
startActivity(logoutIntent);
finish();
} else {
exitAppBackToast = Toast.makeText(getBaseContext(),
R.string.press_back_one_more_time_to_exit, Toast.LENGTH_SHORT);
exitAppBackToast.show();
}
exitAppPressBack = System.currentTimeMillis();
}
This is the code that creates the users when they login anonymously:
public void sellAnonymously(View view) {
circProgressBar.setVisibility(View.VISIBLE);
firebaseAuth.signInAnonymously().addOnSuccessListener(VendorLoginActivity.this, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
Toast.makeText(VendorLoginActivity.this, "Success! Enjoy the app!", Toast.LENGTH_LONG).show();
String client_id = firebaseAuth.getCurrentUser().getUid();
DatabaseReference client_db = FirebaseDatabase.getInstance().getReference().child("Vendor").child(client_id);
client_db.setValue(true);
circProgressBar.setVisibility(View.INVISIBLE);
// Sends anonymous user to VendorMapsActivity
startActivity(new Intent(VendorLoginActivity.this, VendorMapsActivity.class));
}
});
}
To delete a user, call user.delete() as shown in the Firebase documentation on deleting a user:
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.delete()
To delete data from the database for the current user, call removeValue() on a reference to that data as shown in the Firebase documentation on deleting data:
ref.removeValue()
You can combine the two by using Cloud Functions for Firebase. In that case you respond to the user being deleted, by deleting their data from the database. For a short explanation of this, see the Firebase documentation on triggering Cloud Functions when a user is deleted:
exports.deleteUserData = functions.auth.user().onDelete((user) => {
// ...
})
i am implementing Fire-Base Authentication in my app, With SignUp and SignIn, however i am having a problem, every time i fill the form and click on the SignUp button the Fire-Base gives back an error even though the account was successfully created. My code for the SignUp method:
public class Signup extends AppCompatActivity {
EditText input_email,input_pass;
private FirebaseAuth auth;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signup);
//View
input_email = (EditText)findViewById(R.id.id_email);
input_pass = (EditText)findViewById(R.id.id_pass);
//Init Firebase
auth = FirebaseAuth.getInstance();
}
public void iLogin (View v){
startActivity(new Intent(Signup.this, Login.class));
finish();
}
public void iForgot(View v){
//startActivity(new Intent(Signup.this, ForgotPassword.class));
finish();
}
public void iSignUp(View v){
signUpUser(input_email.getText().toString(),input_pass.getText().toString());
}
private void signUpUser(String email, String password) {
auth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if(!task.isSuccessful())
{
Toast.makeText(Signup.this, "Successfully created your account", Toast.LENGTH_LONG).show();
}
else{
Toast.makeText(Signup.this, "ERROR has occurred", Toast.LENGTH_LONG).show();
}
}
});
}
}
So what happens is:
1- The user Opens the app for the first time, the app opens mainly on the MainActivity.class which then redirects the user to the SignUp.class (The one i have added above).
2- The user then fills two fields: (Email) and (Password) and clicks on the SignUp button
3- The SignUp.class (The one i have added above) then creates a new account and shows the Toast: ERROR has occurred, even though the account has been created successfully. In order for the class to display the correct Toast (Successfully created your account) i need to press on the button a second time.
So just to be clear: The first time you click, the account is created and i can see it in Fire-Base Console but the app still gives the wrong Toast, for it to show the correct one i need to click it again, Any thoughts on what's wrong with the code? Thanks in advance!
Just change:
if(!task.isSuccessful())
to:
if(task.isSuccessful())
and it should work. There is a success when task.isSuccessful() is true, so the ! sign make it false. That's why it works for the second time.
I have a login in my project but always when I open my app after I close it, have to login again. I use Firebase to authenticate, but I want something like:
check if user is logged ->
if not - open login,
else - open my main activity"
I don't know if I have to use SQLite or something or exist how to keep session alive even you close the application if user doesn't log out.
SOLVED! found this and works 100%
#Override
public void onStart() {
super.onStart();
// Check if user is signed in (non-null) and update UI accordingly.
FirebaseUser currentUser = fAuth.getCurrentUser();
updateUI(currentUser);
}
private void updateUI(FirebaseUser user) {
if (user != null) {
startActivity(new Intent(Login.this, MainActivity.class));
} else {
//returns to login
}
}
Because you are using Firebase to autenticate, you can easily verify if a user is autenticated like this:
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseAuth.AuthStateListener authListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
firebaseUser = firebaseAuth.getCurrentUser();
if (firebaseUser != null) {
Toast.makeText(MainActivity.this, "You are signed in Firebase!", Toast.LENGTH_SHORT).show();
//Redirect to MainActivity
} else {
Toast.makeText(MainActivity.this, "You are signed out from Firebase!", Toast.LENGTH_SHORT).show();
//Redirect to LoginActivity
}
}
};
No need of SqLite. Hope it helps.
If current user is not null, means is logged in, null otherwise
boolean isUserLoggedIn = FirebaseAuth.getInstance().getCurrentUser() != null;
You can set boolean flag when you login on sharedpreferences, so when you load your application you verify that flag, if it's true call some activity but if it's false call login activity.
Just remember to set flag to false when user logout.