How can I get SAM Interface's object in Kotlin - android

Suppose I am Observing a LiveData in a Fragment and I want to remove observer after I received the data.
eg:
val testLiveData = MutableLiveData<String>()
and Observing as:
testLiveData.observe(this, Observer<String> {
//TODO://Remove this Observer from here
//testLiveData.removeObserver(this)
})
How can I do that? Calling "this" is giving me an instance of the Fragment instead of the current Observer.
However, I can do like this.
testLiveData.observe(this, object : Observer<String>{
override fun onChanged(t: String?) {
testLiveData.removeObserver(this)
}
})
Is there any way to do the same in SAM?

In the first case you cannot access this since it is not guaranteed that every invocation of observe creates a new instance of Observer<String>.
If the lambda does not access any variable within the function where it is defined, the corresponding anonymous class instance is reused between calls (i.e., a singleton Observer is created that is used for every observe call).
Thus, for implementing listeners, the second variant (object : Observer<String>) should be used. This enforces that a new Observer is created every time observe is called, which in turn can then be accessed as this within its implemented methods.

Related

How do I manually call the ViewModel observer second time?

I have two Fragment-s with RecyclerView and LiveData both. When I made some changes in second fragment, I need to update RecyclerView in first fragment.
So after update second fragment I need to trigger onChange method in first fragment.
Code from first fragment:
item_viewmodel.getAllCategoryModel().observe(getViewLifecycleOwner(), new Observer<List<Items>>() {
#Override
public void onChanged(List<Items> myLists) {
//Observer is already registered, but I need to call it manully from second fragment.
}
});
How can I do this?
You can achieve this by using Shared viewmodel, create a instance of viewmodel in both the fragments and use accordingly, for more details visit this page link
You should try use Transformations.switchMap() method, put that the first livedata depends of the second livedata by the method describe above
Here is an example class that holds a typed-in name of a user String (such as from an EditText) in a MutableLiveData and returns a LiveData containing a List of User objects for users that have that name. It populates that LiveData by requerying a repository-pattern object each time the typed name changes.
This ViewModel would permit the observing UI to update "live" as the user ID text changes.
class UserViewModel extends AndroidViewModel {
MutableLiveData<String> nameQueryLiveData = ...
LiveData<List<String>> getUsersWithNameLiveData() {
return Transformations.switchMap(
nameQueryLiveData,
name -> myDataSource.getUsersWithNameLiveData(name));
}
void setNameQuery(String name) {
this.nameQueryLiveData.setValue(name);
}
}
Here more about this...
If you want more specific answer please post all code about your viewModel
I assume best way would be to use by activityViewModels() kotlin property delegate in fragments and share the host activity's Viewmodel for cross fragment communication.
More about usage here

What is the best way to call fragments method from ViewModel in MVVM architecture using live data

For eg, As ViewModel should be loosely coupled we cant pass interface reference to ViewModel to get a callback from Viewmodel and call the method implemented in the fragment.
Also please provide an example or reference to call with methods with two or more params using livedata.
Make a live data variable in ViewModel and attach an observer in Fragment to that variable and whenever there will be a change in data then the function will be automatically invoked.
For Example:
viewModel.loading.observe(viewLifecycleOwner, { loading ->
loading?.let {
// Here, I'm calling a new function named setLoaderVisibility
setLoaderVisibility(loader = binding.shimmerProductDetail, binding.containerBody, isLoaderVisible = loading)
}
})
Feel free to ask if something is unclear.

Why Observers added as observeForever to LiveData must be removed?

I've read on Android LiveData documentation that:
You can register an observer without an associated LifecycleOwner object using the observeForever(Observer) method. In this case, the observer is considered to be always active and is therefore always notified about modifications. You can remove these observers calling the removeObserver(Observer) method.
I'm building an app using MVVM architecture pattern using ViewModel and declaring LiveDatas inside my ViewModel class. At my viewModel I have set a observeForever to a LiveData:
val password by lazy {
MutableLiveData<String>()
}
init {
initObservable()
}
private fun initObservable() {
password.observeForever {
...
}
}
From what I understood from the documentation, I should remove the observer everytime the view that instantiates the ViewModel (with the previous code) was destroyed, right? But shouldn't the Observers be destroyed once the view is destroyed (since the ViewModel instance was instantiated in the view and will also be destroyed)?
"I should remove the observer everytime the view that instantiates the ViewModel (with the previous code) was destroyed, right?"
If you are obsering a LiveData in ViewModel using observeForever(observer):
You should not worry about View's lifecycle, because it is different from ViewModel's life. ViewModel should be able to out-live the View that creates it. Instead, the framework will call onCleared() when the ViewModel is not needed, so that's where you should handle removing the observer.
If you are observing a LiveData in View using observe(lifecyclerowner, observer)
Observers will automatically removed by the framework when the lifecycleowner is destroyed.
"But shouldn't the Observers be destroyed once the view is destroyed (since the ViewModel instance was instantiated in the view and will also be destroyed)?"
This question is more of a Java question than Android.
Think about what it means by "being destroyed". When a View or ViewModel is destroyed by the Android Framework, it does not mean that the object is completely removed from the memory. Your activities and fragments will not be garbage collected as long as there are other objects (such as observer) that has reference to them.
If you call observe(activity, observer), then the Android Framework can track the connection between the activity instance and the observer instance, and therefore it can kill observer when it wants to kill activity. However if you simply call observeForever(observer) there is simply no way for Android Framework to tell which object this observer belongs to.
Implementing Sanlok Lee's answer in a ViewModel, it would look like this:
val password by lazy {
MutableLiveData<String>()
}
private val passwordObserver = Observer<String> {
...
}
init {
initObservable()
}
private fun initObservable() {
password.observeForever(passwordObserver)
}
override fun onCleared() {
password.removeObserver(passwordObserver)
super.onCleared()
}
From what I understood from the documentation, I should remove the
observer everytime the view that instantiates the ViewModel
To achieve this you should instantiate your viewmodel inside the View (Activity, Fragment) and observe the livedata like this
val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
by passing this you tie observing livedata to view's lifecycle, so when View (Activity, Fragment) will be destroyed both viewmodel and observer will be destroyed.

Observers added unexpectedly in LiveData

I am trying to implement Room and Livedata in my application.
I use LiveData to be updated on changes.
I have a ViewModel which returns a LiveData object (returned by Room via a Dao class) and I observe this LiveData in my Views.
I have made breakpoints in each place where I add an observer like that :
mSessionViewModel.sessionsList.observe(mActivity, Observer<List<Session>> { list ->
setSessionList(list!!)
})
However, when I am in debug and I check the mObservers variable of the LiveData object, I see that mIterators increase without the breakpoints where I have the creation of the observers reached (like above) :
Can anyone explain me this behavior? It seems that observers are added even if I don't create new ones...
It could eventually be the number of times a change in the LiveData has been detected but I wouldn't expect the list of observers to increase.
I experienced something similar causing multiple messages to pop up upon navigating back to the same fragment. I fixed it by making sure the binding takes place in onCreate rather than onCreateView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initObservables()
}

MVVM pattern and startActivity

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.

Categories

Resources