Live data observer triggered twice on fragment created - android

The issue that I have is not actually bug or big problem. And all works as it should, but nevertheless it annoys me.
In Fragment pbserver:
viewModel.pageNumbersPosition.observe(viewLifecycleOwner) {
if (it!=null) {
SharedPreferenceHelper.pagesNumber = viewModel.pageNumbersArray.value?.get(it)
DLog.d("Set: ${viewModel.pageNumbersArray.value?.get(it)}}")
//Log shows twice as start
}
}
ViewModel:
class MenuViewModel : ViewModel() {
var pageNumbersArray = MutableLiveData(getPageNumbering())
var pageNumbersPosition = MutableLiveData(pageNumbersArray.value?.indexOf(SharedPreferenceHelper.pagesNumber))
private fun getPageNumbering():Array<String> {
val list = mutableListOf<String>()
for (i in 1..25) {
list.add(i.toString())
}
return list.toTypedArray()
}
}
Spinner:
<Spinner
android:id="#+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="#{viewModel.pageNumbersArray}"
android:selectedItemPosition="#={viewModel.pageNumbersPosition}"/>
What happes is viewModel.pageNumbersPosition.observe triggered twice on start. Once from the initiation of the fragment and second time when the spinner sets. This is actually suppose to happen, but I don't like it when Shared Preference sets twice.

I came across a handy class SingleLiveEvent that we can use instead of LiveData in ViewModel class to send only new updates after subscription.
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
fun call() {
postValue(null)
}
}
This LiveData extension only calls the observable if there's an explicit call to setValue() or call().
Update, primary constructor with parameter:
class SingleLiveEvent<T>(value: T) : MutableLiveData<T>(value) {...}

You can check if there is equal value in your shared to avoid the double set
if (it!=null) {
viewModel.pageNumbersArray.value?.get(it).let{ value ->
if (SharedPreferenceHelper.pagesNumber != value)
SharedPreferenceHelper.pagesNumber = value
}
}

Related

Android Kotlin SingleLiveEvent With Flow

I am using a livedata on viewmodel and flow on repository and data source.
when I tried to connect them to each other and get a data stream as below
that error occurred
Error
java.lang.ClassCastException: androidx.lifecycle.CoroutineLiveData cannot be cast to com.versec.versecko.util.SingleLiveEvent
ViewModel
val singleChat : SingleLiveEvent<ChatRoomEntity> = repository.getChatRooms().asLiveData() as SingleLiveEvent<ChatRoomEntity>
Fragment
viewModel.singleChat.observe(viewLifecycleOwner, Observer {
roomList.add(it)
chatRoomAdapter.changeRooms(roomList)
chatRoomAdapter.notifyDataSetChanged()
})
SingleLiveEvent
class SingleLiveEvent<T> : MutableLiveData<T>() {
companion object {
private val TAG = "SingleLiveEvent"
}
private val pending : AtomicBoolean = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) { Log.w(TAG, "Multiple Observers ,,,")}
super.observe(owner, Observer { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
#MainThread
fun call() {value = null } }
what is the proper way to use both??
From what I understood, the reason you are using SingleLiveData is because you don't want the observer to receive same ChatRoomEntity twice in which case it will be duplicated in the list. A simple solution to this is to maintain the list inside the ViewModel and expose this list to the UI, then you won't have to use workarounds like SingleLiveData.
// ViewModel
private val _chatRoomsLiveData = MutableLiveData(emptyList<ChatRoomEntity>())
val chatRoomsLiveData: LiveData<List<ChatRoomEntity>> = _chatRoomsLiveData
init {
viewModelScope.launch {
repository.getChatRooms().collect {
_chatRoomsLiveData.value = _chatRoomsLiveData.value!! + it
}
}
}
// Fragment
viewModel.chatRoomsLiveData.observe(viewLifecycleOwner, Observer {
chatRoomAdapter.changeRooms(it)
chatRoomAdapter.notifyDataSetChanged()
})

livedata observer called each time when back on fragment

I have fragment1, from which I go to fragment2.
The problem is that I can't go back to fragment1 from fragment2
This is how I handle a button click
val onClickLiveData = MutableLiveData<OnClick>()
fun onClick() {
onClickLiveData.value = OnClick.CLICK
}
This is how I handle transition to fragment2
private fun subscribeToClickCallbacks() {
viewModel.onClickLiveData.observe(viewLifecycleOwner, Observer {
findNavController().navigate(R.id.action_home_fragment_to_repositories_fragment)
})
}
I process the transition back like this
navController.popBackStack()
With the help of debug, I found out that with each transition to fragment1, livedata is called and instantly opens fragment2.
How can the problem be solved? I would be grateful for every answer.
If live data is observer multiple times use SingleEvent for handle this case.
Create one global class for SingleLiveData like this.
class SingleLiveEvent<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, object : Observer<T> {
override fun onChanged(t: 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() {
setValue(null)
}
companion object {
private val TAG = "SingleLiveEvent"
}
}
Now use this class like this,
val onClickLiveData = SingleLiveEvent<OnClick>()

Livedata observer are called forever even with removeObserver

I'm facing an issue which drives me crazy.
I have 4 fragments inside an activity.
The logic is: FragA -> FragB -> FragC -> FragD -> FragA -> ...
I'm connected to websockets which post livedata values.
To navigate from FragB to FragC, I'm waiting an event.
The first time, everything works fine, the websockets is recieved, the event is triggered and I'm going to FragC.
But, the second time (after Frag D -> Frag A), if I go back to fragB, the same event is triggered once again. The user doesn't see FragB, and arrives on FragC.
This is the actual behavior but this is not the one I'm expected.
I have do some research and I think it's because the livedata is trigger twice in is normal behavior. And, it can be only dispatch on main thread, so if my fragment goes in the back stack, it will wait for it to be active again.
I have try to removeObserver in the onDestroyView(), it works and the observer is removed, but once the fragment goes again inside onActivityCreated() and I observe the livedata, the observer is instantanetely triggered... I always use "viewLifecycleOwner" as owner.
Is there any way to cancel a liveData execution if I ever go back on an instanciated fragment?
All my frags extends ScopeFragment :
abstract class ScopedFragment : Fragment(), CoroutineScope {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
My liveData:
class MyLiveDatas {
private val _myLiveData = MutableLiveData<CustomType>()
val myLiveData: LiveData<CustomType>
get() = _myLiveData
fun customTrigger(webSocketMessage: WebSocketMessage) {
val createdCustomType = CreatedCustomType(webSocketMessage)
_myLiveData.post(createdCustomType)
}
}
My Fragment:
class MyFragment: ScopedFragment(), KodeinAware {
override val kodein by closestKodein()
private val myLiveData: MyLiveDatas by instance()
private val myLiveDataObserver = Observer<CustomType> { customType ->
... my actions
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
myLiveDatas.myLiveData.observe(viewLifecycleOwner, myLiveDataObserver)
}
override fun onDestroyView() {
super.onDestroyView()
myLiveDatas.myLiveData.removeObserver(myLiveDataObserver)
// I've also try removeObservers with viewLifecycleOwner
}
}
Thanks a lot!
You need to use custom live data , in case you want single event
this is my custom mutable live data in one of my project and it is working
class SingleLiveEvent<T> : MediatorLiveData<T>() {
private val observers = ArraySet<ObserverWrapper<in T>>()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
val wrapper = ObserverWrapper(observer)
observers.add(wrapper)
super.observe(owner, wrapper)
}
#MainThread
override fun removeObserver(observer: Observer<in T>) {
if (observers.remove(observer)) {
super.removeObserver(observer)
return
}
val iterator = observers.iterator()
while (iterator.hasNext()) {
val wrapper = iterator.next()
if (wrapper.observer == observer) {
iterator.remove()
super.removeObserver(wrapper)
break
}
}
}
#MainThread
override fun setValue(t: T?) {
observers.forEach { it.newValue() }
super.setValue(t)
}
private class ObserverWrapper<T>(val observer: Observer<T>) : Observer<T> {
private var pending = false
override fun onChanged(t: T?) {
if (pending) {
pending = false
observer.onChanged(t)
}
}
fun newValue() {
pending = true
}
}
}
LiveData is analogous to a BehaviorRelay, and replays the last value it was told to hold.
LiveData is not LiveEvent, it's not designed for event dispatching.
A regular event bus, a PublishRelay, or something like EventEmitter are better suited for this problem.
Google has devised LiveData<Event<T>> and EventObserver, but if you ever use observe(lifecycleOwner, Observer { instead of observe(lifecycleOwner, EventObserver { it will misbehave, which shows that it's a code smell (LiveData<Event<T>> does not work with Observer, only EventObserver, but its observe method still accepts Observers.)
So personally I'd rather pull in that library EventEmitter I mentioned above, with the LiveEvent helper class.
// ViewModel
private val eventEmitter = EventEmitter<Events>()
val controllerEvents: EventSource<Events> = eventEmitter
// Fragment
viewModel.controllerEvents.observe(viewLifecycleOwner) { event: ControllerEvents ->
when (event) {
is ControllerEvents.NewWordAdded -> showToast("Added ${event.word}")
}.safe()
}
Try to observe the LiveData at onCreate() of the Fragment lifecycle with lifecycle owner as Activity and remove the observer at onDestroy() of the Fragment lifecycle.
Or if that doesn't workout use Event class.
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
This article describes two ways to achieve what you want.
Alternative 1: Wrap your live data in a class that makes sure the value is only observed once.
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
Alternative 2: Use a custom live data class (SingleLiveEvent) that only emits the value once.

LiveData is observed multiple times inside onClickListener in Android

I have a repository setup like this
class ServerTimeRepo #Inject constructor(private val retrofit: Retrofit){
var liveDataTime = MutableLiveData<TimeResponse>()
fun getServerTime(): LiveData<TimeResponse> {
val serverTimeService:ServerTimeService = retrofit.create(ServerTimeService::class.java)
val obs = serverTimeService.getServerTime()
obs.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).unsubscribeOn(Schedulers.io())
.subscribe(object : Observer<Response<TimeResponse>> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: Response<TimeResponse>) {
val gson = Gson()
val json: String?
val code = t.code()
val cs = code.toString()
if (!cs.equals("200")) {
json = t.errorBody()!!.string()
val userError = gson.fromJson(json, Error::class.java)
} else {
liveDataTime.value = t.body()
}
}
override fun onError(e: Throwable) {
}
})
return liveDataTime
}
}
Then I have a viewmodel calling this repo like this
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
fun getServerTime(): LiveData<TimeResponse> {
return serverTimeRepo.getServerTime()
}
}
Then I have an activity where I have an onClickListener where I am observing the livedata, like this
tvPWStart.setOnClickListener {
val stlv= serverTimeViewModel.getServerTime()
stlv.observe(this#HomeScreenActivity, Observer {
//this is getting called multiple times??
})
}
I don't know what's wrong in this. Can anyone point me in the right direction? Thanks.
Issue is that every time your ClickListener gets fired, you observe LiveData again and again. So, you can solve that problem by following solution :
Take a MutableLiveData object inside your ViewModel privately & Observe it as LiveData.
class ServerTimeViewModel #Inject constructor(private val serverTimeRepo: ServerTimeRepo):ViewModel() {
private val serverTimeData = MutableLiveData<TimeResponse>() // We make private variable so that UI/View can't modify directly
fun getServerTime() {
serverTimeData.value = serverTimeRepo.getServerTime().value // Rather than returning LiveData, we set value to our local MutableLiveData
}
fun observeServerTime(): LiveData<TimeResponse> {
return serverTimeData //Here we expose our MutableLiveData as LiveData to avoid modification from UI/View
}
}
Now, we observe this LiveData directly outside of ClickListener and we just call API method from button click like below :
//Assuming that this code is inside onCreate() of your Activity/Fragment
//first we observe our LiveData
serverTimeViewModel.observeServerTime().observe(this#HomeScreenActivity, Observer {
//In such case, we won't observe multiple LiveData but one
})
//Then during our ClickListener, we just do API method call without any callback.
tvPWStart.setOnClickListener {
serverTimeViewModel.getServerTime()
}

Multiple calls to set LiveData is not observed

I have recently seen a weird issue that is acting as a barrier to my project.
Multiple calls to set the live data value does not invoke the observer in the view.
It seems that only the last value that was set actually invokes the Observer in the view.
Here is the code snippet for a review.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModelImpl::class.java)
viewModel.state().observe(this, Observer {
onStateChange(it!!)
})
viewModel.fetchFirstThree()
}
private fun onStateChange(state: MainViewModel.State) {
when (state) {
is One -> {
show(state.data)
}
is Two -> {
show(state.data)
}
is Three -> {
show(state.data)
}
}
}
private fun show(data: String) {
Log.d("Response", data)
}
}
MainViewModel.kt
abstract class MainViewModel : ViewModel() {
sealed class State {
data class One(val data: String) : State()
data class Two(val data: String) : State()
data class Three(val data: String) : State()
}
abstract fun state(): LiveData<State>
abstract fun fetchFirstThree()
}
MainViewModelImpl.kt
class MainViewModelImpl : MainViewModel() {
private val stateLiveData: MediatorLiveData<State> = MediatorLiveData()
override fun state(): LiveData<State> = stateLiveData
override fun fetchFirstThree() {
stateLiveData.value = State.One("One")
stateLiveData.value = State.Two("Two")
stateLiveData.value = State.Three("Three")
}
}
Expected output:
Response: One
Response: Two
Response: Three
Actual Output:
Response: Three
As per the output above, the Observer is not being called for the first two values.
I did some science, re-implementing LiveData and MutableLiveData to log out some data.
Check the source code here.
setValue value=Test1
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test2
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test3
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
ITEM: Test3
It looks like the observer hasn't reached an active state when you send the initial values.
private void considerNotify(LifecycleBoundObserver observer) {
// <-- Three times it fails here. This means that your observer wasn't ready for any of them.
if (!observer.active) {
return;
}
Once the observer reaches an active state, it sends the last set value.
void activeStateChanged(boolean newActive) {
if (newActive == active) {
return;
}
active = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += active ? 1 : -1;
if (wasInactive && active) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !active) {
onInactive();
}
if (active) {
// <--- At this point you are getting a call to your observer!
dispatchingValue(this);
}
}
I had such issue too.
To resolve it was created custom MutableLiveData, that contains a queue of posted values and will notify observer for each value.
You can use it the same way as usual MutableLiveData.
open class MultipleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private val values: Queue<T> = LinkedList()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(this::class.java.name, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, { t: T ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
//call next value processing if have such
if (values.isNotEmpty())
pollValue()
}
})
}
override fun postValue(value: T) {
values.add(value)
pollValue()
}
private fun pollValue() {
value = values.poll()
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#Suppress("unused")
#MainThread
fun call() {
value = null
}
}
You could use custom LiveData like this:
class ActiveMutableLiveData<T> : MutableLiveData<T>() {
private val values: Queue<T> = LinkedList()
private var isActive: Boolean = false
override fun onActive() {
isActive = true
while (values.isNotEmpty()) {
setValue(values.poll())
}
}
override fun onInactive() {
isActive = false
}
override fun setValue(value: T) {
if (isActive) {
super.setValue(value)
} else {
values.add(value)
}
}
}
FWIW I had the same problem but solved it like this...
I originally had some code similar to this...
private fun updateMonth(month: Int){
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
updateMonth(1)
updateMonth(2)
updateMonth(3)
I experienced the same problem as described...
But when I made this simple change....
private fun updateMonth(month: Int) {
CoroutineScope(Dispatchers.Main).launch {
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
}
Presumably, each updateMonth is going onto a different thread now, so all of the updates are observed.
You should call viewModel.fetchFirstThree() after Activity's onStart() method. for example in onResume() method.
Because in LiveData the Observer is wrapped as a LifecycleBoundObserver. The field mActive set to true after onStart().
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
#Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);// return true after onStart()
}
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());// after onStart() change mActive to true
}
}
When the observer notify the change it calls considerNotify, before onStart it will return at !observer.mActive
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {// called in onCreate() will return here.
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}

Categories

Resources