How to recreate ViewModel instead of reading previous one - android

As we can find in Google site, it says:
If the activity is re-created, it receives the same MyViewModel instance that was created by the first activity. When the owner activity is finished, the framework calls the ViewModel objects's onCleared() method so that it can clean up resources.
So, right now, I do not want that feature. I want to recreate the ViewModel each time the Fragment gets created. Because I have a reference of the object using the id.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SaleViewModel viewModel = ViewModelProviders.of(this).get(SaleViewModel.class);
viewModel.getSaleLiveData().observe(this, new Observer<List<SaleEntity>>() {
#Override
public void onChanged(#Nullable List<SaleEntity> saleEntities) {
initAll();
}
});
}
Inside SaleViewModel, I have the reference of the Store (which is set activities before):
private static FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
private static DatabaseReference HOT_STOCK_REF = FirebaseDatabase.getInstance().getReference("users").child(user.getUid()).child(FirebaseStoreRef.getInstance().getStoreEntity().getId()).child("income");
private FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF);
With that being said, I need to recreate the ViewModel every single time because of the reference I need to refresh (otherwise, I will be getting the first reference set).
Thank you in advance.

Related

Why do we need LiveData and ViewModel

I have used LiveData and ViewModel example
but i dont understand use of this feature because i can change value directly without use this feature even this is growing number of line in code by using observing code and same as in ViewModel by creating MutableLiveData.
below ViewModel Code
public class FirstViewModel extends ViewModel {
// Create a LiveData with a String
public MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<String>();
}
return mCurrentName;
}
}
Using in Activity
public class MainActivity extends AppCompatActivity {
private FirstViewModel mModel;
ActivityMainBinding mBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding=DataBindingUtil.setContentView(this,R.layout.activity_main);
// Get the ViewModel.
mModel= ViewModelProviders.of(this).get(FirstViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
#Override
public void onChanged(#Nullable final String newName) {
// Update the UI, in this case, a TextView.
mBinding.mNameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getCurrentName().observe(this, nameObserver);
mBinding.btnSubmit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String anotherName = mBinding.etField.getText().toString();
mModel.getCurrentName().setValue(anotherName);
}
});
}
}
The ViewModel and LiveData android architecture components together help to create lifecycle aware applications.
ViewModel:
ViewModel classes are often used to significantly segregate the view logic (present in Activity classes) from the business logic which is contained in the ViewModel classes. This segregation is a good architecture design and becomes very important while maintaining large projects.
LiveData:
LiveData helps in implementing the Observer Observable pattern in a lifecycle aware manner.
In your case, it may seem trivial since you are only setting value for a TextView. However consider common scenarios like hitting an api to retrieve data, etc. In such cases, the ViewModel is responsible for providing the data to be displayed in the Activity, which when done with the help of LiveData can help avoid crashes by ensuring lifecycle awareness easily.
You can read about live data from here. It is like Observer that looks for changing of data and notify observers that observable object has changed
In simple words its make your life eazy as a programmer when we go into the details like activity/fragment lifecycle handling, displaying updated data and more importantly separating the presentation layer from business logic and to create a more well structured application. please find more details from here

Am I allowed to observe a ViewModel, if I clean up the back references?

The suggested way to implement ViewModel is to expose the changing data by using LiveData objects to activities, fragments and views. There are cases, when LiveData is not an ideal answer or no answer at all.
The natural alternative would be, to apply the observer pattern to the ViewModel, make it an observable. When registering observers to the ViewModel, the ViewModel will hold callback references to notify the observers.
The documentation says, a ViewModel must not hold references to activities, fragments or views. The only answer to the question "why" I found is, that this may cause memory leaks. Then how about cleaning up the references to avoid memory leaks?
For views this is a difficulty. There is no defined moment, when the view goes away. But activities and fragments have a defined lifecycle. So there are places to unregister as observers.
What do you think? Is it valid to register activities as observers to ViewModels if you take care to always unregister them? Did you hit upon any valid information about this question?
I set a small reward for the best answer. It's not because I think it a recommended solution (as it does not work with views). I just want to know and extend my options.
public class ExampleViewModel extends ViewModel {
public interface OnEndListener {
public void onEnd();
}
private List<OnEndListener> onEndListeners = new ArrayList<>();
public void setOnEndListener(OnEndListener onEndListener) {
onEndListeners.add(onEndListener);
}
public void removeOnEndListener(OnEndListener onEndListener) {
onEndListeners.remove(onEndListener);
}
public void somethingHappens() {
for (OnEndListener onEndListener: new ArrayList<OnEndListener>(onEndListeners) ) {
onEndListener.onEnd();
}
}
}
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
ExampleViewModel.OnEndListener onEndListener;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onEndListener = new ExampleViewModel.OnEndListener() {
#Override
public void onEnd() {
finish();
}
};
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.setOnEndListener(onEndListener);
}
#Override
protected void onDestroy() {
super.onDestroy();
exampleViewModel.removeOnEndListener(onEndListener);
}
}
To ask "am I allowed..." is not really a useful question, IMO. The docs are clear that what you are suggesting is discouraged and why. That said, I expect that your code would probably work as expected and is therefore "allowed" (i.e. not prevented by a technical constraint).
One possible gotcha scenario: InstanceA of ExampleActivity is started and kicks off some long-running task on the ExampleViewModel. Then, before the task completes, the device is rotated and InstanceA is destroyed because of the configuration change. Then, in between the time when InstanceA is destroyed and a new InstanceB is created, the long-running task completes and your view model calls onEndListener.onEnd(). Except: Oh no! The onEndListener is null because it was cleared when InstanceA was destroyed and hasn't yet been set by InstanceB: NullPointerException
ViewModel was designed (in part) precisely to handle edge cases like the gotcha scenario above. So instead of working against the intended use of the ViewModel, why not just use the tools it offers along with LiveData to accomplish the same thing? (And with less code, I might add.)
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.getOnEndLive().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean onEnd) {
if (onEnd != null && onEnd) {
finish();
}
}
});
}
}
public class ExampleViewModel extends ViewModel {
private MutableLiveData<Boolean> onEndLive = new MutableLiveData<>();
public MutableLiveData<Boolean> getOnEndLive() {
return onEndLive;
}
public void somethingHappens() {
onEndLive.setValue(true);
}
}
Think of the LiveData in this case not as actual "data" per se, but as a signal that you can pass from your ViewModel to your Activity. I use this pattern all the time.

How can you access managed objects created by different Realm instances?

I have created an application in Android that uses realm.
I have a splash screen that loads all the data needed to operate, and a number of activities that use that data.
That data is stored in an Application class extension as static variables.
My problem is that when I try to manipulate the data in other activities I get an error about the realm database being closed.
I have solved this by creating a static reference to my database on Application. Opening it in OnCreate() and closing it in OnTerminate() but I get the feeling that this is wrong.
I have also solved it by creating unmanaged objects (i.e. after retrieving them from Realm using copyFromRealm) and then when I need to do alterations copying them back into a realm instance I just created.
What is the correct way to solve this problem?
I have a splash screen that loads all the data needed to operate, and a number of activities that use that data.
That data is stored in an Application class extension as static variables.
That's very interesting - have you tried to put the application in background on a screen, then go to Android Studio's Logcat tab, go to the additional menu items in the bottom left, and click TERMINATE?
Fun fact is that if your application is in background when you do this, and then you re-open the app from the launcher, you'll have the exact same effect as if your app had been killed via low memory condition.
In your case, you'll just crash with an NPE on pretty much any screen that is not the splash screen! Very, very interesting.
(Also, Application.onTerminate() is never called. So that's not helpful either.)
Anyways, to get back to your original question,
My problem is that when I try to manipulate the data in other activities I get an error about the realm database being closed.
What is the correct way to solve this problem?
The section Best Practices: Controlling the lifecycle of Realm instances section of the official documentation is fairly helpful in this regard.
public class MyActivity extends Activity {
private Realm realm;
private RealmResults<Dog> dogs;
private RealmChangeListener<RealmResults<Dog>> realmChangeListener = (results) -> {
// do something on first load + future changes of Dogs
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
setContentView(R.layout.activity_main);
dogs = realm.where(Dog.class).sort("age").findAllAsync();
dogs.addChangeListener(realmChangeListener);
}
#Override
protected void onDestroy() {
super.onDestroy();
dogs.removeAllChangeListeners();
dogs = null;
realm.close();
realm = null;
}
}
or
public class MyFragment extends Fragment {
private Realm realm;
private RealmResults<Dog> dogs;
private RealmChangeListener<RealmResults<Dog>> realmChangeListener = (results) -> {
// do something on first load + future changes of Dogs
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
View root = inflater.inflate(R.layout.fragment_view, container, false);
return root;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
dogs = realm.where(Dog.class).sort("age").findAllAsync();
dogs.addChangeListener(realmChangeListener);
}
#Override
public void onDestroyView() {
super.onDestroyView();
dogs.removeAllChangeListeners();
dogs = null;
realm.close();
realm = null;
}
}
Realm instances are ref-counted on a given thraead, so I guess the answer is, "keep one open while you need it". And of course, close it when you no longer do.

What happens to static members of an Activity when it is recreated because of config changes

I want to create a static presenter object in my Activity, so that when the Activity is recreated because of config changes, it will retain the presenter instance and my business logic will not be affected.
The code of my Activity is:
public class HomeActivity extends AppCompatActivity {
public static HomePresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
if (presenter == null){
presenter = new HomePresenter();
}
}
}
Nothing will happen to the static instance. But doing this could leak memory (see Avoiding memory leaks) if you do not delete the reference to the static presenter.
I would suggest another approach. Override onRetainNonConfigurationInstance() to keep an object when the Activity is destroyed because of an configuration change (e.g. rotation). And use getLastNonConfigurationInstance() to get the very same object after the configuration change.
public class HomeActivity extends AppCompatActivity {
public HomePresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
presenter = (HomePresenter)getLastNonConfigurationInstance();
if (presenter == null){
presenter = new HomePresenter();
}
}
#Override
public Object onRetainNonConfigurationInstance() {
return presenter;
}
}
You can also use a Fragment to keep objects alive during a configuration change, see RetainingAnObject.
Your code will work, presenter will be alive, but, please, don't do this.
Keyword 'static' means that value of this field will be attached to class, not to instance of it. So if you for example will have your HomeActivity, then you go to the SomeElseActivity and then go to new HomeActivity (you will have back stack HomeActivity -> SomeElseActivity -> HomeActivity) for new HomeActivity you will have same presenter as for old one. Thus you will have one share presenter for 2 independent instances of HomeActivity. Moreover, if you have some state in presenter, you will have a lot of problems with your application in this case.
I recommend you to remove 'static' keyword. And if your presenter have state, that's needed to be saved during config changes, try one of 2 alternatives:
1) Create onSaveInstanceState and onRestoreInstance state in your presenter and call them in appropriate activity's methods
2) Create fragment without ui, but with flag 'retain instace' (setRetainInstance method), and this fragment will keep reference to your presenter.
In short, the static object remains, and it gets its birth when you class is loaded into memory, and never goes away until your app dies, or when the class is unloaded.
In JVM languages, the compiler optimizes static fields by embedding the value in the bytecode instead of computing the value at runtime.
When you fire up a JVM and load a class for the first time (this is done by the classloader when the class is first referenced in any way) any static blocks or fields are 'loaded' into the JVM and become accessible.
So the static variable lives in the circle in the snapshot, and it is ignorant of whatever config changes, it is there, no matter what happens and as long as as the class is loaded.

how can i persist a model between multiple activities in android?

I'm a beginner to android development, and I'm trying to write my code in an MVC pattern, but I'm having trouble understanding how a model would work. As far as I can tell every time you start a new activity with an intent you are not able to pass a model along with it. As far as i can tell you'd have to reinitialize it each time you start a new activity. Am I missing something? I looked into Parcelable, but it seems that you loose your methods if you make your model Parcelable. right now I'm building a log in system, which checks my local sqllite db on start up if the user has already logged in, and if so it passes to another activity, otherwise it passes to the log in activity, but I wan't to keep that user model alive through all the activities. Is thee a way to do that?
You might want to also consider keeping a static reference around to the model data that you want to share across activities so that you don't have to keep serializing/deserializing the model when switching between activities. You can get away with using Parcelable if your models are small, but at some point, performance may become an issue.
I'm working on a project where we keep the models in a Singleton that we can access throughout the app, and although I generally hate Singleton's for how they can make unit testing more difficult, I have found this approach to perform better with larger models than trying to rely on Android's serialization mechanism.
Here's is a very rough example of what I mean (disclaimer: I have not actually run tested this code, but I hope this illustrates the concept):
You might have a singleton class that I terribly called Models
public class Models {
private static Models instance;
private boolean isInitialized = false;
private User user;
private OtherInterestingModel otherInterestingModel;
private Models() {
}
public static synchronized Models getInstance() {
if (instance == null) {
instance = new Models();
}
return instance;
}
public void loadModels() {
if (!isInitialized) {
/*
* One-time model initialization here.
*/
isInitialized = true;
}
}
public User getUser() {
return user;
}
public OtherInterestingModel getOtherInterestingModel() {
return otherInterestingModel;
}
}
In your LoginActivity, you can initialize the Models class, say, in your onCreate():
public class LoginActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
/*
* This might be called after the user enters data and clicks a login button...
*/
private void login() {
startActivity(new Intent(this, AwesomeLoggedInActivity.class));
}
}
Once the user successfully logs into your app, you could have basically the same code in your main activity:
public class AwesomeLoggedInActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
}
Notice that by having a Singleton, you avoided having to serialize the model data by passing it through the intent that started the main activity.
Yes, you can do that with the Parcelable interface.
You do not lose your class's methods when you implement the Parcelable interface. The interface simply defines a method for writing your member variables to a Parcel object when you need to pass the object around.
Once you retrieve the data from your Intent via getParcelableExtra(), the object is recreated from the Parcel and you can once again treat it as an instance of whatever class it is.
For example, if you have a User class that extends Parcelable, you can bundle it with an Intent by calling putExtra("user", myUser). myUser is then (behind the scenes) packed into a Parcel and attached to the Intent. In your next Activity, you can retrieve that User object with User myUser = (User) getParcelableExtra("user");, and the Parcel will be unpacked and returned to you. You wil once again have a fully functioning User object.

Categories

Resources