I have the following scenario: I have a model class, which looks like this:
public class UserModel implements Serializable {
private String userEmail, userName;
public UserModel() {}
public UserModel(String userEmail, String userName) { this.userEmail = userEmail; this.userName = userName;}
//setters and getters
}
In my first activity, I'm logging in to Firebase, I'm getting the data from the FirebaseUser object and then I'm creating an object of the UserModel class and passing it to intent like this:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("userModel", userModel);
startActivity(intent);
In the second activity, every time it starts, I'm checking if the user is logged in or not. If it is, it stays here, if not I redirect to the first activity. Here, I'm getting the object using the following line of code:
UserModel userModel = (UserModel) getIntent().getSerializableExtra("userModel");
First time when the activity starts, everything works fine, but when I restart the activity, I get a NullPointerException. How can I preserve the userModel object that I got from the intent through any activity restarts?
Thanks in advance!
You should have a look at the Activity Lifecycle. You'll find a method called onSavedInstanceState(). Now what you want to do is store the object of your class in the bundle using that method, and get it back in onCreate(), as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
UserModel userModel = (UserModel) savedInstanceState.getSerializable("userModel");
} else if (getIntent() != null) {
UserModel userModel = (UserModel) getIntent().getSerializableExtra("userModel");
} else {
// These is no data
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("userModel", userModel);
}
You have to persist the model to external storage. Some viable options are:
SharedPreferences: save in onPause(), restore in onResume() or onCreate(); here is a quick example.
SaveInstanceState
For example through SaveInstanceState by overriding the following callbacks:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
/* savedInstanceState Bundle will be passed onto onCreate() when activity
* gets killed and restarted. */
savedInstanceState.putSerializable("userModel", userModel);
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
/* Also being passed onto onCreate(). */
UserModel mUserModel = (UserModel) savedInstanceState.getSerializable("userModel");
}
By doing a null-check against the intent first, you can save yourself some processing time, while not doing any redundant operations.
Related
I am creating a Journal app.
I am currently working on the functionality for if the user is already logged in—bypass the "get started/log in activities"
In the video I am watching to create a journal app, the instructor calls
mUser = firebaseAuth.getCurrentUser(); several times.
He calls it in onStart(), in onCreate() and in onAuthStateChanged(). I can understand why we might need to call it again in onAuthStateChanged(), but in this case, I'm just checking if the user is already logged in, so it shouldn't change from the user received in onCreate()
I removed it from onAuthStateChanged() and onStart() and everything is still working fine. However, I'm unsure if it will lead me to errors in the future. If anyone can confirm this, I would appreciate it.
Is there a reason why we need to call getCurrentUser() several times?
Thanks.
This is my full code for reference:
public class MainActivity extends AppCompatActivity {
Button getStarted;
private FirebaseUser mUser;
private FirebaseAuth firebaseAuth;
private FirebaseAuth.AuthStateListener myListener;
private FirebaseFirestore db = FirebaseFirestore.getInstance();
private CollectionReference myCollectionRef = db.collection("Users");
#Override
protected void onCreate(Bundle savedInstanceState) {
firebaseAuth = FirebaseAuth.getInstance();
mUser = firebaseAuth.getCurrentUser();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getStarted = findViewById(R.id.btnGetStarted);
getStarted.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, LoginActivity.class));
finish();
}
});
myListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull #NotNull FirebaseAuth firebaseAuth) {
if(mUser != null){
//logged in
//I commented this out and everything is still working fine
//mUser = firebaseAuth.getCurrentUser();
String currentUserId = mUser.getUid();
myCollectionRef.whereEqualTo("userId", currentUserId).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable #org.jetbrains.annotations.Nullable QuerySnapshot value, #Nullable #org.jetbrains.annotations.Nullable FirebaseFirestoreException error) {
if(error != null){
Log.d("my_error", error.toString());
}
if(!value.isEmpty()){
for(QueryDocumentSnapshot snapshot : value){
JournalApi myJournalApi = new JournalApi();
myJournalApi.setUsername(snapshot.getString("username"));
myJournalApi.setId(snapshot.getString("userId"));
startActivity(new Intent(MainActivity.this, JournalList.class));
finish();
}
}
}
});
}else{
//not logged in
}
}
};
}
#Override
protected void onStart() {
super.onStart();
//I commented this out and everything is still working fine
//mUser = firebaseAuth.getCurrentUser();
firebaseAuth.addAuthStateListener(myListener);
}
#Override
protected void onPause() {
super.onPause();
if(firebaseAuth != null){
firebaseAuth.removeAuthStateListener(myListener);
}
}
}
While signing in is an active process, that your code triggers by calling one of the signInWith methods, maintaining the authentication state and restoring it on application restart are background processes that happen automatically when you use the Firebase SDK. This is great, because it means you don't have to write code to keep tokens valid, or to check if the user profile has changed or their account has been disabled.
It does mean however that you can't reliably cache the value of firebaseAuth.getCurrentUser() in a variable for much time. If you keep the value in a variable, and then the SDK updates the authentication state in the background, your code may not be looking at the correct value for firebaseAuth.getCurrentUser() anymore.
That's why you'll see more calls to firebaseAuth.getCurrentUser() than you might expect, and also why you'll see firebaseAuth.addAuthStateListener in places that want to get notified when the authentication state changed, like when the user is signed in or out by the SDK.
You've to call this method everytime you want to check the user's current session due the user's session can be changed from another service or method, if you don't call this method and just call once, you won't have the latest user's profile info, auth session, and such more.
I am building a sign-in activity (Firebase, Google sign in) that only starts on the application's first run. Problem is that activity persists on spawning even after the authentication process is complete.
I am combining Realtime Database for storing user data into the database when a new user gets registered.
I've run a debugger through the authentication process, which returned no errors or unexpected behavior. I can also confirm that Realtime Database gets queried as expected.
Application is written in the combination of Java and Kotlin.
This is how I call the SignInActivity inside MainActivity on application's first run (Java)
private SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Check if this is application's first run
sharedPreferences = getSharedPreferences(getApplicationContext().getPackageName(), Context.MODE_PRIVATE);
startActivity(new Intent(MainActivity.this, SignInActivity.class));
}
#Override
protected void onResume() {
super.onResume();
if (sharedPreferences.getBoolean("first", true)) {
startActivity(new Intent(MainActivity.this, SignInActivity.class));
sharedPreferences.edit().putBoolean("first", false).apply();
}
}
and this is a Kotlin function that commences the authentication process
private fun authenticateWithGoogle(account: GoogleSignInAccount?) {
if (account != null) {
val credential: AuthCredential = GoogleAuthProvider.getCredential(account.idToken, null)
firebaseAuth.signInWithCredential(credential).addOnCompleteListener{
if (it.isSuccessful) {
databaseReference.addValueEventListener(object: ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
databaseReference.child(account.id!!)
val userModel: UserModel? = dataSnapshot.getValue(UserModel::class.java)
if (userModel == null) {
databaseReference.child(account.id!!).setValue(UserModel(account.id, account.displayName, account.email))
startActivity(Intent(this#SignInActivity, MainActivity::class.java))
}
startActivity(Intent(this#SignInActivity, MainActivity::class.java))
}
})
}
}
}
}
Upon the initial transition from MainActivity to SignInActivity, the authentication process begins on button press. Function authenticateWithGoogle gets called as expected and the database is queried for information whether a user exists or not. If the user exists, just transition back to MainActivity, if not, store their data to the database and then transition to MainActivity.
At this point, shared preference for storing the state of the first run in MainActivity should be set to false, but apparently, it's not, hence why SignInActivity gets called again.
Any sort of help would be very appreciated.
I fixed the problem differently, as suggested by shubham-vashisht.
So, here's how I did it:
private SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Check if it's application's first run
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean state = sharedPreferences.getBoolean("firstrun", false);
if (!state) {
Editor editor = sharedPreferences.edit();
editor.putBoolean("firstrun", true);
editor.apply();
startActivity(new Intent(MainActivity.this, SignInActivity.class));
}
}
When I need a callback from Activity B back to Activity A I usually make the reference variable in Activity B 'static'. I realize that if the user rotates the device the Life Cycle methods will remove my reference.
Is this the only drawback and is there a better way to register without a static reference. Is it better to simply put all data in the Application class ? - Thank you.
public class MainActivity extends Activity implements InterfaceMainActivityTwo {
static Main2Activity main2Activity;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
main2Activity = new Main2Activity();
main2Activity.setDataListener(this);
}
#Override
public void getDataMainActivityTwo(String string) {
tvTextData.setText(string);
}
}
public class Main2Activity extends Activity {
static InterfaceMainActivityTwo mGetDataInterface;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
public void getDataSaveBtn(View v) {
if (mGetDataInterface != null)
mGetDataInterface.getDataMainActivityTwo(fullName);
else
Toast.makeText(this, "IS NULL.INTERFACE NOT INITIALIZED !!!!", Toast.LENGTH_SHORT).show();
}
/////////// interface setup
interface InterfaceMainActivityTwo {
void getDataMainActivityTwo(String string);
}
public void setDataListener(InterfaceMainActivityTwo listener) {
this.mGetDataInterface = listener;
}
}
You should never need a callback between two activities. You're doing something wrong if you do. If you need to pass data from A to B, pass it in the bundle. If you need to pass it back from B to A, use startActivityForResult and pass it in the result. If you need to share data between many activities, it should be held in some globally accessible data structure, either in memory or on disk.
I am trying to keep my HashMap values when I navigate to another activity and return. This is the code I have for now.
The HashMap works and is able to grab and save the data from the EditText in the view.
However as soon as I leave from the activity and return, the HashMap is reinitialized to empty -> {}
I have looked at documentation and it seems this is the correct way of ensuring that a variable data is persisted. However it does not work.
please let me know what could be the issue:
public class ScriptActivity extends MainActivity {
HashMap timeAndMessages;
EditText message;
EditText time;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_script);
if (savedInstanceState != null) {
timeAndMessages = (HashMap) savedInstanceState.getSerializable("alerts");
} else {
timeAndMessages = new HashMap();
}
message = (EditText)findViewById(R.id.messageText);
time = (EditText)findViewById(R.id.timeText);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
restore(savedInstanceState);
}
private void restore(Bundle savedInstanceState) {
if (savedInstanceState != null) {
timeAndMessages = (HashMap) savedInstanceState.getSerializable("alerts");
}
}
public void createMessage (View view){
String stringmessage = message.getText().toString();
int inttime = Integer.parseInt(time.getText().toString());
timeAndMessages.put(inttime, stringmessage);
Toast.makeText(getApplicationContext(), "Will display : " + stringmessage + " At time : " + Integer.toString(inttime) , Toast.LENGTH_LONG).show();
}
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putSerializable("alerts", timeAndMessages);
}
}
However as soon as I leave from the activity and return, the HashMap is reinitialized to empty -> {}
If by "leave from the activity and return", you mean press the BACK button, then do something to start a fresh activity... then your behavior is expected.
The Bundle for the saved instance state is used in two main scenarios:
Configuration changes (e.g., user rotates the screen)
Process termination, and the user returns to your recent task (e.g., via the overview screen)
Pressing BACK to destroy the activity is neither of those. Hence, the state is not saved.
If this HashMap represents model data — the sort of data that you expect to be able to get back to, time and again, no matter how the user uses your app — save it to a database, SharedPreferences, other sort of file, or "the cloud".
You can read more about these scenarios in the Activity documentation.
so i get the main idea of how to use
protected void onSaveInstanceState (Bundle outState)
http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)
also from Saving Android Activity state using Save Instance State
but my problem is what if this was the first time the application is being created? then nothing would have been stored in the bundle before....and if so then when i try to call something out from the bundle that hasn't been saved before what do i get?null?
for example
i have this in my code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String [] b=savedInstanceState.getStringArray("MyArray");
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
String [] a={"haha"};
savedInstanceState.putStringArray("MyArray", a);
}
in the FIRST time the application is ever opened what would the value of b be?
and after the application has been used once what would the value of b be?
Many thanks!
in your onCreate()
add a condition
if(savedInstanceState==null){
//meaning no data has been saved yet or this is your first time to run the activity. Most likely you initialize data here.
}else{
String [] b=savedInstanceState.getStringArray("MyArray");
}
by the way to retrieve data that was saved in your onSaveInstanceState you will override this
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
}
You have to check for null always in the onCreate() or in onRestoreInstanceState() like below:
String [] b = new String[arraysize];
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
b = savedInstanceState.getStringArray("MyArray");
// Do here for resetting your values which means state before the changes occured.
}
else{
default..
}
Here you do general things.
}