This question already has answers here:
One time login in app - FirebaseAuth
(3 answers)
Closed 1 year ago.
Before you remove my question from stackoverflow, please hear me out.
I know that problem is very common but none that i tried helped me. i already saw available solutions on stackoverflow and other websites but they didn't resolved my issue.
So even if you choose to remove my question from this forum please help me resolve my question first, atleast mail me.
button1.setOnClickListener(view -> {
login();
});
}
public void login() {
Intent intent = new Intent(this, IndexActivity.class);
String mail = et1.getText().toString();
String password = et2.getText().toString();
if (mail.isEmpty()) {
error.setText(e1);
} else if (password.isEmpty()) {
error.setText(e2);
} else if (password.length() < 6) {
error.setText("Invalid Password Length!!");
et2.setError("Password length must be at least 6!!");
} else {
mAuth.signInWithEmailAndPassword(mail, password).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(MainActivity.this, "Welcome Back",Toast.LENGTH_LONG).show();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
error.setText("");
et1.setText("");
et2.setText("");
}
else {
Toast.makeText(MainActivity.this, " "+ Objects.requireNonNull(task.getException()).getMessage(),Toast.LENGTH_LONG).show();
error.setText(e3);
}
});
}
if (error.getText().toString().isEmpty()) {
error.setVisibility(View.INVISIBLE);
} else {
error.setVisibility(View.VISIBLE);
}
}
}
This above is my login page
and below is my child activity
logout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mAuth.getCurrentUser() != null) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
mAuth.signOut();
startActivity(intent);
finish();
}
else {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
}
}
});
i have tried everything on internet. sharedPreferences, onBackPressed override, onSaveInstanceState, onRestoreInstanceState, onStart and onResume override, but i don't know what exactly to use.
check if user is already logged in or not by
if (auth.getCurrentUser() != null)
//user logged in already, do your work here for logged in user
else
//user is not logged in, let user login
The back button most likely does not log the user out, but rather the UI elements have not updated with the user information.
If you suspect the user is being logged out, this would be an Auth refresh, which will trigger an onAuthStateChanged() event if you have one registered.
Otherwise, checking the current auth for the current user with auth().currentUser should yield null or a user object
Get current user
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// User is signed in
} else {
// No user is signed in
}
Auth State Listener
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
} else {
// User is signed out
}
Log.d("LOG_Login", "onAuthStateChanged:signed_out");
}
}
};
mAuth.addAuthStateListener(mAuthListener);
Related
I want to make a login program using Firebase authentication using an email and password. First, I created a register page with the class name "RegisterActivity." When the user successfully registers, the user will then be thrown to the login page. Everything was running smoothly until I wrote the following code in the LoginActivity class.
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
// Check whether there are users who have logged in or not logged out.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null){
Intent intent = new Intent(LoginActivity.this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
finish();
}
}
};
After I apply the code above, when the user successfully registers on the register page, the user is automatically thrown to the home page instead of the login page again.
I know maybe I have to delete the code above, but if the code is deleted, then every time the user closes the application, the user will be asked to re-login. So, how do solve this issue without removing the preceding code?
This is the code snippet in the RegisterActivity class.
mAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
progressBar.setVisibility(View.GONE);
if (task.isSuccessful()){
Toast.makeText(RegisterActivity.this, "Register succces", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}else{
Toast.makeText(RegisterActivity.this, "Register failed", Toast.LENGTH_SHORT).show();
}
}
});
I have a LoginActivity that is not tied to any online database, I only validate the username via edit text component to login and move to the MainActivity. LoginActivity appears when the value of text in MainActivity is null, therefore I created a validation method when the username is null, then the layout will move to LoginActivity, I made it like this with the aim that LoginActivity does not become the main layout of the app.
Validation users in MainActivity.java:
private void checkUsername() {
Intent intent = getIntent();
if(intent.getExtras() != null){
String users = intent.getStringExtra(LoginActivity.EXTRA_USER);
txuser.setText(users);
//NullPointerException: Attempt to invoke virtual method 'long android.database.sqlite.SQLiteDatabase.insert(java.lang.String)
ContentValues cv = new ContentValues();
cv.put(Contract.UserEntry.NAME_COLUMN,users);
database.insert(Contract.UserEntry.TABLE_NAME,null,cv);
} else {
startActivity(new Intent(this, LoginActivity.class));
}
}
LoginActivity.java
private void loginUser() {
final String username;
username = user.getText().toString();
if (TextUtils.isEmpty(username)) {
Toast.makeText(this, "Name cannot be blank!", Toast.LENGTH_SHORT).show();
}
if (!cbAgree.isChecked()) {
Toast.makeText(this, "Login fails!", Toast.LENGTH_SHORT).show();
}
fabLogin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(LoginActivity.this, UtamaActivity.class);
intent.putExtra(EXTRA_USER, username);
startActivity(intent);
}
});
}
How do I fix this?, because when I open the app for the first time, the LoginActivity is successfully displayed, but when I enter to MainActivity it immediately forces close. I have included the logcat comment in the checkUsername() method
Change Intent intent = new Intent(LoginActivity.this, UtamaActivity.class);
to Intent intent = new Intent(LoginActivity.this, MainActivity.class);
I'm working on an Android app that does the following:
Upon app start-up, it checks if a user is logged in, using AuthStateListener.
If there is a user logged in, it retrieves data from Firestore. The user data is stored in a document that I named with the following nomenclature: "User " + user's_email_ID. For example, if a user has an email ID xyz#gmail.com, his data will be stored in the document named: User xyz#gmail.com.
All documents are within the collection named "Users".
If all the fields are null/ empty in the user's data document, the app opens an Activity that asks him/her to fill all the details. Else, it takes the user to the main page (StudentMainActivity if the user is a student, or ProfessorMainActivity if the user is a professor).
Coming to my problem:
The block of code which checks whether the fields are empty has some erratic and unpredictable behavior. I'm not sure if this is a problem based on Firestore, or on the fact that data retrieval happens on a different thread.
I checked the Firestore database and saw that all fields were filled. However, when a user (who's already logged in) starts the app, the app knows that it is the same user (i.e. he's not prompted to sign in, because AuthStateListener does its job), but instead of being redirected to either StudentMainActivity or ProfessorMainActivity (the main screens), he's asked to fill his details again.
What's more confusing is that this bug doesn't always occur. There are times when the app does what is expected, i.e. take the user to the main screen, but the next time he starts the app, he's again taken to the activity that asks him to enter his details.
Source Code:
LoginActivity.java (Only the relevant parts)
//AuthStateListener is in onCreate
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null){
UIDEmailID = user.getEmail();
updateUI(user);
}
else{
updateUI(null);
}
}
};
private void updateUI(FirebaseUser user){
// Update UI after login
if (user != null) {
Toast.makeText(LoginActivity.this, "User " + UIDEmailID, Toast.LENGTH_LONG).show();
db.collection("Users").document("User " + UIDEmailID).get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.get("department") != null || // if any
documentSnapshot.get("phoneNumber") != null || // field in
documentSnapshot.get("name") != null || // Firestore is
documentSnapshot.get("studentSemester") != null || // non-null then
documentSnapshot.get("dateOfBirth") != null || // proceed to
documentSnapshot.get("university") != null) { // further activities
if (documentSnapshot.get("userType") == "Lecturer/ Professor") {
Intent intent = new Intent(LoginActivity.this, ProfessorMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
else {
Intent intent = new Intent(LoginActivity.this, StudentMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
} else {
Toast.makeText(LoginActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(LoginActivity.this, GFBDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(LoginActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
I'm sorry for the long question; I just tried to make it super descriptive. Some help would be greatly appreciated.
P.S. The reason I think this is a problem involving the usage of multiple threads is because whenever the app runs as expected (i.e. takes the user to the main screen), the toast "We need some additional details before we go ahead." appears as well. If you look at the code (the last "else" block) you will realise that it is in a seperate conditional block altogether, and thus isn't even supposed to show up if the main screen (which is in another conditional block) shows up.
EDIT 1:
I'm enclosing screenshots pertaining to the problem. Ignore the bland UI :P
This is what's expected (Comes under the second 'else' block). It is supposed to show up only if the user is logging in for the first time, i.e. does not have his data stored in a Firestore document.
The background is StudentMainActivity (inside the nested 'else'). However, even the Toast is displayed (it belongs to a seperate block altogether).
So it turns out Firestore wasn't (entirely) at fault.
Every activity in an Android application has a life span, and every time an activity is run, it goes through an elaborate sequence of lifecycle functions.
An activity's lifecycle is as follows:
Launched --> onCreate() --> onStart() --> onResume() --> Running --> onPause() --> onStop() --> onDestroy() --> Finished
I won't be digressing by going into the details of each function, because the function names are quite intuitive and self-explanatory.
As you can see in the code snippet in the question, onAuthStateChanged() is inside onCreate(). My Document ID on Firebase is of the form "User UIDEmailID", where UIDEmailID is the email ID of the user. And UIDEmailID gets updated only in onAuthStateChanged() (which, in turn, is inside onCreate()), i.e. only when the activity starts afresh, after the app has been closed and opened again.
Therefore, I updated UIDEmailID in onStart() as well, which means that every time an app is resumed, it will retrieve the email ID of the user, which can subsequently be used to retrieve the document from Firestore.
Also, I slightly tweaked my Firestore data retrieval bit of code upon advice from Nibrass H. The solution is as follows:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
running = true;
if (savedInstanceState != null){
running = savedInstanceState.getBoolean("running");
wasrunning = savedInstanceState.getBoolean("wasrunning");
}
setContentView(R.layout.splash_screen);
firebaseAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth1) {
FirebaseUser user = firebaseAuth1.getCurrentUser();
if (user != null){
UIDEmailID = user.getEmail();
updateUI(user);
} else {
updateUI(null);
}
}
};
}
#Override
protected void onStart() {
super.onStart();
firebaseAuth.addAuthStateListener(authStateListener);
if (firebaseAuth.getCurrentUser() != null) {
UIDEmailID = firebaseAuth.getCurrentUser().getEmail();
updateUI(firebaseAuth.getCurrentUser());
} else {
updateUI(null);
}
}
#Override
protected void onRestart() {
super.onRestart();
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth1) {
FirebaseUser user = firebaseAuth1.getCurrentUser();
if (user != null) {
UIDEmailID = user.getEmail();
updateUI(user);
} else {
updateUI(null);
}
}
};
}
#Override
protected void onPause() {
super.onPause();
wasrunning = running;
running = false;
}
#Override
protected void onResume() {
super.onResume();
if (wasrunning){
running = true;
}
}
#Override
protected void onStop() {
super.onStop();
if (authStateListener != null) {
firebaseAuth.removeAuthStateListener(authStateListener);
}
}
private void updateUI(FirebaseUser firebaseUser){
if (firebaseUser != null){
Toast.makeText(this, "User " + firebaseUser.getEmail(), Toast.LENGTH_SHORT).show();
db.collection("Users").document("User " + UIDEmailID).get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (documentSnapshot.get("userType") != null) {
if (documentSnapshot.get("userType").equals("Lecturer/ Professor")){
Intent intent = new Intent(SplashScreenActivity.this, ProfessorMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
} else {
Intent intent = new Intent(SplashScreenActivity.this, StudentMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
} else {
Toast.makeText(SplashScreenActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SplashScreenActivity.this, GFBDetailsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
}
});
}
}
I have my login setup like this, I use retrofit to authenticate from the server and I'm using a token api authentication:
In the MainActivity.java method onCreate
apiInterface = ApiClient.getClient().create(ApiInterface.class);
User user = SharedPreferencesHelper.getUser(MainActivity.this);
if (user.getToken() == null) {
Intent login = new Intent(MainActivity.this, LoginActivity.class);
startActivity(login);
} else {
setContentView(R.layout.activity_main);
buildMain();
}
In the LoginActivity.java method on create (I'll summarize, the code is quite long)
loginbtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//get login ingo
Login login = new Login(scardI, passwordI, device_name);
Call<User> call = apiInterface.LoginUser(login);
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
//save user info into SharedPreferences
SharedPreferencesHelper.setUser(LoginActivity.this, user);
//check if user saved correctly by getting the user token
if (SharedPreferencesHelper.getUserToken(LoginActivity.this) != null) {
finish();
}
} else {
//show error message
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
//show error message
}
});
}
});
I'm having an issue, after the user logs in a blank page is displayed instead of the activity_main.xml however when I close the app and reopen it it takes me straight to the activity_main.xml as expected. Is there a reason that after the login it doesn't take me to the activity_main like expected.
An intent to MainActivity.java is missing on success. A blank screen is displayed because finish() is called.
if (response.isSuccessful()) {
User user = response.body();
SharedPreferencesHelper.setUser(LoginActivity.this, user);
if (SharedPreferencesHelper.getUserToken(LoginActivity.this) != null){
Intent i = new Intent(this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
startActivity(i);
}
}
I'm writing this question and answer because I haven't seen a full solution to the integration of Google sign in on Android using Facebook's Parse SDK (or Sashido in my case) as a back-end without cloud code.
Related Questions:
How to link Google + signed in users on Parse backend on Android?
Google Plus Login issues - Parse.com
How would one go about integrating Google Sign in with Parse back-end without Cloud Code?
First of all, follow the steps provided by Android Developers on starting and implementing the integration.
Start Integrating Google Sign-In into Your Android App
Integrating Google Sign-In into Your Android App
In the onCreate your activity you need to build the GoogleSignInOptions
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
You can get your web_client_id when you add Google Services to your project to your Google Developers account. Find out more at: Creating a Google API Console project and client ID
Build your GoogleApiClient (make it a global instance private GoogleApiClient mGoogleApiClient;)
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.e("Failed", "failed" + connectionResult.getErrorMessage());
}
})
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Listen out for the click on the dedicated button for your Google sign in and then start a Auth.GoogleSignInApi.getSignIntent(mGoogleApiClient);
case R.id.btn_google:
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
break;
Make sure you've assigned a value to RC_SIGN_IN (I've done 1000)
Now start adding implementation to your onActivityResult method
// Result returned from launching the Intent from
// GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
} else {
ParseFacebookUtils.onActivityResult(requestCode, resultCode, data);
}
Now to handle the sign in request:
`private void handleSignInResult(GoogleSignInResult result) {
Log.e("handleSignIn", "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
// Signed in successfully, show authenticated UI.
final GoogleSignInAccount acct = result.getSignInAccount();
if (acct != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("_User");
query.setLimit(10000);
query.whereEqualTo("email", acct.getEmail());
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> objects, ParseException e) {
if (e == null) {
if (objects.size() == 0) {
saveNewUserGoogle(acct);
} else {
loginGoogleUser(objects.get(0), acct);
}
} else {
saveNewUserGoogle(acct);
}
}
});
}
} else {
Log.e("failed", "failed to sign in");
// Signed out, show unauthenticated UI.
}
}`
So what this method does is if the request to the GoogleSignIn Request is successful, get the account details, query the _User table in your database and to see if the email with the account matches. If it does, Log the user in.
private void loginGoogleUser(ParseObject j, GoogleSignInAccount acct) {
ParseUser.logInInBackground(j.getString("username"), String.valueOf(acct.getId()), new LogInCallback() {
public void done(ParseUser user, ParseException e) {
if (user != null) {
Intent i = new Intent(AllLoginActivity.this, MainActivity.class);
startActivity(i);
finish();
} else {
Log.e("failed", "could not be validated");
}
}
});
}
else sign the user up:
private void saveNewUserGoogle(GoogleSignInAccount acct) {
google = true;
final ParseUser user = new ParseUser();
String mFullName = acct.getDisplayName();
String mEmail = acct.getEmail();
String mProfilePic = String.valueOf(acct.getPhotoUrl());
String mUsername = acct.getId();
String password = acct.getId();
user.setUsername(mUsername);
user.setEmail(mEmail);
user.setPassword(password);
user.put("userEmail", mEmail);
user.put("uniqueID", mUsername);
user.put("name", mFullName);
user.put("loginMethod", "Google");
user.put("profilePicture", mProfilePic);
user.signUpInBackground(new SignUpCallback() {
public void done(ParseException e) {
if (e == null) {
Log.e("SaveTest", "Successful");
//sign user up
} else {
switch (e.getCode()) {
case ParseException.USERNAME_TAKEN:
Toast.makeText(context, "Sorry, this username has already been taken.", Toast.LENGTH_SHORT).show();
break;
case ParseException.USERNAME_MISSING:
Toast.makeText(context, "Sorry, a username is needed", Toast.LENGTH_SHORT).show();
break;
case ParseException.PASSWORD_MISSING:
Toast.makeText(context, "Sorry, a password is needed.", Toast.LENGTH_SHORT).show();
break;
case ParseException.OBJECT_NOT_FOUND:
Toast.makeText(context, "invalid credentials", Toast.LENGTH_SHORT).show();
break;
case ParseException.CONNECTION_FAILED:
Toast.makeText(context, "Sorry, internet is needed.", Toast.LENGTH_SHORT).show();
break;
default:
Log.d("Testing", e.getLocalizedMessage());
break;
}
}
}
});
}
So for this if you set the password on Parse as the clientID it'll be unique to that user and can be read by Parse and given by Google.
Note: I'm setting the username as the Google Identifier and then when they have successfully connected and signed up I display a username dialog box where they enter a username, so it can be displayed as something in plain text rather than numerics.