When the Dark mode is enabled using the AppCompatDelegate.MODE_NIGHT_YES in kotlin, the activity or the fragment is recreated. If we are calling an API in the same class it will be recalled. Is there a way to eliminate recalling the API.
The ideal way to handle this is by using ViewModel.
As the official documentation says:
The ViewModel class is designed to store and manage UI-related data in
a lifecycle conscious way. The ViewModel class allows data to survive
configuration changes such as screen rotations.
What it means, is that your ViewModel will survive your UI recreation and there will be no data loss or API double calls.
Create your ViewModel class.
class MyViewModel : ViewModel() {
fun callAPI() {
// call you're API here
}
}
Just obtain your ViewModel once in Fragment's onCreate() lifecycle callback.
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
As you want that your API call will be done onle once per screen, make it in the ViewModel's construcor (for example).
init {
callAPI()
}
Please read more about this here.
Related
onRetainCustomNonConfigurationInstance was deprecated in AndroidX when the first version got released back in 2018! As the AndroidX Activity library says in the release notes:
onRetainCustomNonConfigurationInstance has been deprecated. Use a ViewModel for storing objects that need to survive configuration changes.
I just want a single object to survive configuration changes, this was the purpose of onRetainCustomNonConfigurationInstance!
For my use case, I want an instance of a Dagger graph to survive configuration changes in an Activity but using AAC ViewModel doesn't feel right to me for that use case. Dagger injects my ViewModels, I don't want to wrap my graph in another ViewModel just for the sake of making it survive configuration changes.
Is there any other way I can make an object survive configuration changes?
Using ViewModel is the most accurate and recommended way to make an object survive configuration changes, you should use it. You could've used onSaveInstanceState but that'd force all the objects needing to support Parcelable, that's not only reasonable, it's sometimes impossible.
To replace onRetainCustomNonConfigurationInstance, you can use the same APIs that support ViewModel in a way that handles all the complexity for you.
You can use this implementation of LongLastingElement API (code here) that uses ViewModel under the hood and removes all the boilerplate code to make an object survive configuration changes.
For your example, to make the Dagger graph survive configuration changes with this API, the code would look like this:
class LoginActivity : AppCompatActivity() {
private lateinit var loginComponent: LoginComponent
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loginComponent = LongLastingElement.getInstance<LoginComponent>(this).getElement {
(applicationContext as MyDaggerApplication).appComponent.loginComponent().create()
}
loginComponent.inject(this)
}
}
LongLastingElement has a static method called getInstance where you specify the type of the object that needs to be stored and you pass a lifecycle owner in. Then, the getElement method is called, it receives a lambda as a parameter that needs to create an instance of the object you want to store.
Since the Lifecycle owner is used to obtain the LongLastingElement instance, the lambda that getElement takes as a parameter will be only called once. It'll create an instance of the object the very first time is called and the same instance will be reused after configuration changes and subsequent calls to getElement. This works for any lifecycle owner such as Activities and Fragments.
I have an activity that uses the ViewModel architecture component:
class RandomIdViewModel : ViewModel() {
var currentId : MutableLiveData<String?> = MutableLiveData()
init {
currentId.value = UUID.randomUUID().toString()
}
}
And then in my Activity I have this in the onCreate() method:
viewModel = ViewModelProviders.of(this).get(RandomIdViewModel::class.java)
viewModel.currentId.observe(this, idObserver)
Every time I rotate my phone the Id changes. So I am fairly confused as to why init is being called when I set the viewModel object.
EDIT
I have been looking at the saving state UI guidelines and it definitely appears that the ViewModel should maintain it's data throughout simple configuration changes:
ViewModel is ideal for storing and managing UI-related data while the user is actively using the application. It allows quick access to UI data and helps you avoid refetching data from network or disk across rotation, window resizing, and other commonly occurring configuration changes. ...
ViewModel is ideal for storing and managing UI-related data while the user is actively using the application. It allows quick access to UI data and helps you avoid refetching data from network or disk across rotation, window resizing, and other commonly occurring configuration changes
It appears that having a global variable in the activity that is stores a reference to the ViewModel as a once off causes the issue. All the examples seem to use the VM in a local variable, which doesn't work for me (I don't want my observers to be declared inline as it starts making the code quite messy1). The local variable seems to get a new instance every time a config change occurs. However if I create a method:
private fun viewModel() = ViewModelProviders.of(this).get(RandomIdViewModel::class.java)
and I call this whenever I need the VM. I think this is a bug that will most likely be resolved in the future.
1As a side note I also need to point out that I also had to remove my observers when the activity was not using them. This was another reason why I couldn't just inline the definition of the observers as they happen in different lifecycle events:
override fun onResume() {
super.onResume()
viewModel().currentId.observe(this, idObserver)
}
override fun onPause() {
viewModel().currentId.removeObserver(idObserver)
super.onPause()
}
One promise of the ViewModel is, that it survives cases like rotation of the screen. I still try to figure out how to organise this in practice.
On certain events of the model the View should update. There are two major options:
The ViewModel updates the View.
The View observes the ViewModel and updates itself.
In the first case the ViewModel needs a link to the View. I could inject the View into the ViewModel, yet my feeling is it would be better to inject the VieModel into the View.
What is the better style to join them?
Then after rotation the onCreate() method is called again triggering initialisations of the ViewModel a second time. I need to check for this else I am in danger to register listeners to the actual model twice and thrice and similar issues. I may even need to clean up relations to the old view first.
This checking feels kind of unclean. I would expect a dedicated API for this in the ViewModel, if this would be a standard practice. Without I have the feeling to be on the wrong track.
What are good patterns to deal with this in a clean standard way?
Soo.. You don't really have to connect the ViewModel and the Activity/Fragment "with respect to screen rotation", you get that for free - that's one of the perks.
The official documentation is really good.
You connect a ViewModel to your view in onCreate() by something like
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
updateUI()
});
}
}
And while it is true as you say that orientation change will trigger onCreate() again, it's not true that this will create a new ViewModel. The MyViewModel is only created the first time around in onCreate. Re-created activities receive the same MyViewModel instance created by the first activity. This is even true for different fragments/activities referencing the same ViewModel.
You should never ever inject a view into the ViewModel. It's the equivalent of drowning puppies. If you need a context in the ViewModel, extend AndroidViewModel instead (and pass it the Application).
What you do is that you create a ViewModel that holds all state. And handles fetching data from network or disk or what not. All that is not UI related goes in to the ViewModel (as a rule of thumb). All view updating stuff goes into the activity/fragment.
A ViewModel for the example above might look like
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
This often means that click events should probably be passed down into the ViewModel so that it can massage the data. And the view will just react to the updated (massaged) data.
I recently decided to have a closer look at the new Android Architecture Components that Google released, especially using their ViewModel lifecycle-aware class to a MVVM architecture, and LiveData.
As long as I'm dealing with a single Activity, or a single Fragment, everything is fine.
However, I can't find a nice solution to handle Activity switching.
Say, for the sake of a short example, that Activity A has a button to launch Activity B.
Where would the startActivity() be handled?
Following the MVVM pattern, the logic of the clickListener should be in the ViewModel. However, we want to avoid having references to the Activity in there. So passing the context to the ViewModel is not an option.
I narrowed down a couple of options that seem "OK", but was not able to find any proper answer of "here's how to do it.".
Option 1 : Have an enum in the ViewModel with values mapping to possible routing (ACTIVITY_B, ACTIVITY_C). Couple this with a LiveData.
The activity would observe this LiveData, and when the ViewModel decides that ACTIVITY_C should be launched, it'd just postValue(ACTIVITY_C). Activity can then call startActivity() normally.
Option 2 : The regular interface pattern. Same principle as option 1, but Activity would implement the interface. I feel a bit more coupling with this though.
Option 3 : Messaging option, such as Otto or similar. ViewModel sends a Broadcast, Activity picks it up and launches what it has to. Only problem with this solution is that, by default, you should put the register/unregister of that Broadcast inside the ViewModel. So doesn't help.
Option 4 : Having a big Routing class, somewhere, as singleton or similar, that could be called to dispatch relevant routing to any activity. Eventually via interface? So every activity (or a BaseActivity) would implement
IRouting { void requestLaunchActivity(ACTIVITY_B); }
This method just worries me a bit when your app starts having a lot of fragments/activities (because the Routing class would become humongous)
So that's it. That's my question. How do you guys handle this?
Do you go with an option that I didn't think of?
What option do you consider the most relevant and why?
What is the recommended Google approach?
PS : Links that didn't get me anywhere
1 - Android ViewModel call Activity methods
2 - How to start an activity from a plain non-activity java class?
NSimon, its great that you start using AAC.
I wrote a issue in the aac's-github before about that.
There are several ways doing that.
One solution would be using a
WeakReference to a NavigationController which holds the Context of the Activity. This is a common used pattern for handling context-bound stuff inside a ViewModel.
I highly decline this for several reasons. First: that usually means that you have to keep a reference to your NavigationController which fixes the context leak, but doesnt solve the architecture at all.
The best way (in my oppinion) is using LiveData which is lifecycle aware and can do all the wanted stuff.
Example:
class YourVm : ViewModel() {
val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>()
fun onClick(item: YourModel) {
uiEventLiveData.value = item to 3 // can be predefined values
}
}
After that you can listen inside your view for changes.
class YourFragmentOrActivity {
//assign your vm whatever
override fun onActivityCreated(savedInstanceState: Bundle?) {
var context = this
yourVm.uiEventLiveData.observe(this, Observer {
when (it?.second) {
1 -> { context.startActivity( ... ) }
2 -> { .. }
}
})
}
}
Take care that ive used a modified MutableLiveData, because else it will always emit the latest result for new Observers which leads to bad behaviour. For example if you change activity and go back it will end in a loop.
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}
Why is that attempt better then using WeakReferences, Interfaces, or any other solution?
Because this event split UI logic with business logic. Its also possible to have multiple observers. It cares about the lifecycle. It doesnt leak anything.
You could also solve it by using RxJava instead of LiveData by using a PublishSubject. (addTo requires RxKotlin)
Take care about not leaking a subscription by releasing it in onStop().
class YourVm : ViewModel() {
var subject : PublishSubject<YourItem> = PublishSubject.create();
}
class YourFragmentOrActivityOrWhatever {
var composite = CompositeDisposable()
onStart() {
YourVm.subject
.subscribe( { Log.d("...", "Event emitted $it") }, { error("Error occured $it") })
.addTo(compositeDisposable)
}
onStop() {
compositeDisposable.clear()
}
}
Also take care that a ViewModel is bound to an Activity OR a Fragment. You can't share a ViewModel between multiple Activities since this would break the "Livecycle-Awareness".
If you need that persist your data by using a database like room or share the data using parcels.
You should call startActivity from activity, not from viewmodel. If you want to open it from viewmodel, you need to create livedata in viewmodel with some navigation parameter and observe on livedata inside the activity
You can extend your ViewModel from AndroidViewModel, which has the application reference, and start the activity using this context.
I have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample