I have two Activities.
LoginActivity
HomeScreen
I also have a splash screen with this setting:
<activity android:name=".SplashScreen"
android:theme="#style/AppTheme.NoActionBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
The Splash Screen then transfers the user to the HomeScreen by using this code:
Intent intent = new Intent(SplashScreen.this, HomeScreen.class);
Inside the HomeScreen Activity, I check if the user is already signed in or not. If he's not signed in, I transfer him to the LoginActivity:
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(FirebaseAuth firebaseAuth) {
if(firebaseAuth.getCurrentUser() == null)
{
Intent intent = new Intent(HomeScreen.this,LoginActivity.class);
Toast.makeText(HomeScreen.this,"H>L on no login",Toast.LENGTH_LONG).show(); //toast to show me why my app is in infinite loop
startActivity(intent);
}
}
};
On the LoginActivity, I have a simple Login button which uses FireBase Google Authentication. Also, I check if the user is already signed in or not, and if he is, he's transferred to HomeScreen Activity:
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
if(firebaseAuth.getCurrentUser() != null)
{
Intent intent = new Intent(LoginActivity.this,HomeScreen.class);
Toast.makeText(LoginActivity.this,"L>H already signed in",Toast.LENGTH_LONG).show();
startActivity(intent);
}
}
};
And, also:
private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
Log.d(TAG, "firebaseAuthWithGoogle:" + acct.getId());
AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Intent intent = new Intent(LoginActivity.this,HomeScreen.class);
Toast.makeText(LoginActivity.this,"L>H on login",Toast.LENGTH_LONG).show();
startActivity(intent);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCredential:failure", task.getException());
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
}
});
}
Now, the problem is: When I open the app, I get a splash screen and I'm transferred to the Login page. Then when I press back, I get the Login page AGAIN instead of exiting the app. Also, once I'm singed in: I get the splash screen and I'm taken to the HomeScreen. Then if I press back, I am taken to the HomeScreen AGAIN instead of exiting the app.
Please help me out. I searched StackOverflow, but didn't quite reach anywhere. Any help is appreciated. Ask me for updates if you need more info! :)
Okay, So I found a solution. Apart from the precious replies below, it was also necessary to add a flag to the intent saying FLAG_ACTIVITY_CLEAR_TOP.
This is the working code:
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Intent intent = new Intent(LoginActivity.this,HomeScreen.class);
Toast.makeText(LoginActivity.this,"L>H on login",Toast.LENGTH_LONG).show();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCredential:failure", task.getException());
Toast.makeText(LoginActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
}
I am not sure it is correct, please try,
Instead of calling startActivity(new Intent(HomeScreen.this,LoginActivity.class)) from onAuthStateChanged(), simply call finish(). Hope it may help you.
Simply do like this.
img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
You need to implement onBackPressed() and by default your app should exit. However, to exit your app you can use
getActivity.finish();
or
finishAndRemoveTask(); - it finishes all activities and removes it from recent tasks list
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'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 am getting this error when transtioning from a fragment to an activity as shown below:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference at android.content.ComponentName.<init>(ComponentName.java:130) at android.content.Intent.<init>(Intent.java:6108)
Below is my code for going to the next activity, The error occurs on the first line of the code below.
Intent mainIntent = new Intent (getContext(), MainActivity.class);
mainIntent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity (mainIntent);
I don't see any solution so far online.
It seems that you're getting a wrong context that raises this NullPointerException
Try to replace the below line:
Intent mainIntent = new Intent (getContext(), MainActivity.class);
With: >> if you're within an activity
Intent mainIntent = new Intent (this, MainActivity.class);
with: >> if you're within a callback listener within the activity
Intent mainIntent = new Intent (MyActivityName.this, MainActivity.class);
With: >> if you're within a fragment
Intent mainIntent = new Intent (requireActivity(), MainActivity.class);
Please try with getActivity() intsead of getContext()
I work the transaction of the fragment with this code
private Context context;
context.startActivity(new Intent(context, MainActivity.class));
I have been able to find a work around this. I realised I am getting the error because I am creating the new Intent inside the firebase OnCompleteListener as shown below. So after i removed it from inside the listener and called it outside, my program works properly.
I instead created a global boolean variable that I updated to true on successful data storage. Then I can access it somewhere else and then transtion to another if true.
#BEFORE
PostsRef.child(key).updateChildren(postsMap)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(getActivity(), "New Post is updated successfully.", Toast.LENGTH_SHORT).show();
Intent mainIntent = new Intent (getActivity(), MainActivity.class);
mainIntent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity (mainIntent);
} else {
Toast.makeText(context, "Error occured while updating your post.", Toast.LENGTH_SHORT).show();
}
}
});
#AFTER
PostsRef.child(key).updateChildren(postsMap)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
mSuccess = true;
Toast.makeText(getActivity(), "New Post is updated successfully.", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "Error occured while updating your post.", Toast.LENGTH_SHORT).show();
}
}
});
Hello guys I have also realised one of the big causes of this error is the lifecycle methods.So I was overriding them inside my fragment to update the user state on the firebase database. Because of this they caused the android.content.Context.getPackageName() on a null object reference error whenever I tried to move to the main activity managing it. Incase I used the fragment transition to move to another fragment, the MainActivity would get excecuted more than twice. After stopping to override them my application worked properly. If someone would explain why that happens it would be great.
#Override
public void onStart() {
super.onStart();
updateUserState("Online");
}
#Override
public void onResume() {
super.onResume();
updateUserState("Online");
}
Since, you are calling from a Fragment, you should use getActivity() here instead of getContext()
Intent mainIntent = new Intent (getActivity(), MainActivity.class);
mainIntent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity (mainIntent);
This should help I hope.
Sometimes startActivity method throws an exception like:
Calling startActivity() from outside of an Activity context requires
the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
So you should intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) method
I try to create this app for my lesson , It sign in successfully but when i try to change the Activity to another one , app crashes. this is my Login method , its in LoginActivity.java and in onCreate() method
private void Login(String email, String password) {
auth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this , new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
else {
Toast.makeText(LoginActivity.this, "Authentication failed!", Toast.LENGTH_SHORT).show();
}
}
});
}
The value of task.isSuccessful() is true , and sign in is successfully , but can`t change the activity.
As per your crash logs,
You are getting NPE on setting an action bar title.
since your theme is No action bar, so it will give NPE.
Just remove below the line, no crash would occur
getSupportActionBar().setTitle("Login");
In my Firebase application I created a fragment that allows users to update their information like namn & email and so far all is going well, however my issue is after the user have updated the information - the changes are not visible untill next relaunch of the application.
How can I reflect the changes directly from the databse without promoting the User to relaunch the app?
I have created a Method called restart(); that will like the name says says restart the application - But still the changes are not being reflected!
/**
* Update Name Only
*/
private void updateDisplayNameOnly() {
showProgress();
AuthCredential credential = EmailAuthProvider
.getCredential(FirebaseAuth.getInstance().getCurrentUser().getEmail(), mConfirm.getText().toString());
FirebaseAuth.getInstance().getCurrentUser().reauthenticate(credential)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
UserProfileChangeRequest profileUpdate = new UserProfileChangeRequest.Builder()
.setDisplayName(mName.getText().toString())
//.setPhotoUri(Uri.parse("https://avatarfiles.alphacoders.com/862/86285.jpg"))
.build();
user.updateProfile(profileUpdate);
Log.d(TAG, "onComplete: User Profile updated");
Toast.makeText(getActivity(), "Name is updated", Toast.LENGTH_SHORT).show();
restartApp();
} else {
Toast.makeText(getActivity(), "Name was not updated", Toast.LENGTH_SHORT).show();
}
hideProgress();
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
hideProgress();
Toast.makeText(getActivity(), "You have entered wrong password", Toast.LENGTH_SHORT).show();
}
});
}
Restart Method
public void restartApp() {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
}
Firebase has a listener onDataChange(), so when you query data from firebase, make sure you implement that, see doc. If you want to reflect the change, implement it in this method (like resetting the text fields). There is no need for a restart method.
Update profile call is asynchronous, and its results are not available immediately, so for some time you'll still observe "obsolete" data.
When you call updateProfile, you get a task as a result. You can subscribe on completion of this task, and if it's completed successfully, then you will be able to get updated data from user instance. E.g.:
final FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
Log.i("MyActivity", "before updateProfile: username=" + currentUser.getDisplayName());
UserProfileChangeRequest profileUpdate = new UserProfileChangeRequest.Builder()
.setDisplayName("UPDATED_NAME")
.build();
final Task<Void> task = currentUser.updateProfile(profileUpdate);
Log.i("MyActivity", "after updateProfile: username=" + currentUser.getDisplayName());
task.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Log.i("MyActivity", "onComplete: username=" + currentUser.getDisplayName());
}
});
And here is an output:
I/MyActivity: before updateProfile: username=old name
I/MyActivity: after updateProfile: username=old name
I/MyActivity: onComplete: username=UPDATED_NAME
Also there is a method user.reload(), which force reload user data from Firebase server. This is useful when your client user cache is obsolete for some reason. It is also an asynchronous method, which gives you Task and you need to subscribe on its completion.