Avoid LiveData triggered twice

515 Views Asked by At

I have such issue:

Two fragments: A and B, which inject viewModel, but for some reason I have result of LiveData in both of my fragments. How can I avoid triggering live data pushing me old value? How to reset liveData somehow or ignore old values? Thanks. In both of them I am listening to LiveData changes like this:

@AndroidEntryPoint
class LoginFragment : MyBaseDebugFragment(R.layout.spinner_layout) {

    @Inject
    private val viewModel: AuthorizationViewModel by activityViewModels()
    
    {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    viewModel.loginActionLD.observe(viewLifecycleOwner) { loginStatus ->
    //Do something regarding the value of login status.
    }
    viewModel.DoLogin()
}
    
    
    @HiltViewModel
    class AuthorizationViewModel @Inject constructor(
        private val login: LoginUseCase,
        private val logout: LogoutUseCase,
        @Dispatcher.IO private val ioDispatcher: CoroutineDispatcher,
        @Scope.Application private val externalScope: CoroutineScope,
    ) : ViewModel() {
        private val _loginActionLD = MutableLiveData<LoginAction>()
        val loginActionLD: LiveData<LoginAction> = _loginActionLD
    
            fun DoLogin(from: AuthRequest) {
            launchOnMyExternalScope {
                _loginActionLD.postValue(login().toLoginAction(from))
            }
        }
    
        private fun launchOnMyExternalScope(block: suspend CoroutineScope.() -> Unit) =
            externalScope.launch(ioDispatcher, block = block)
    
        }
    }
    
    
    @Module
    @InstallIn(SingletonComponent::class)
    object CoroutineScopeModule {
    
        @Singleton
        @Scope.Application
        @Provides
        fun provideApplicationScope(@Dispatcher.IO ioDispatcher: CoroutineDispatcher): CoroutineScope =
            CoroutineScope(SupervisorJob() + ioDispatcher)
    
    }
    
    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class Scope {
    
        @Qualifier
        @Retention(AnnotationRetention.BINARY)
        annotation class Application
    
    }
2

There are 2 best solutions below

0
On

There is a handy class SingleLiveEvent that you can use instead of LiveData in your 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().

0
On

Here is what helped me to avoid LiveData to trigger twice it's handler. This code is tested carefully:

open class LiveEvent<T> : MediatorLiveData<T>() {

    private val observers = ArraySet<OneTimeObserver<in T>>()

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val wrapper = OneTimeObserver(observer)
        observers.add(wrapper)
        super.observe(owner, wrapper)
    }

    @MainThread
    override fun observeForever(observer: Observer<in T>) {
        val wrapper = OneTimeObserver(observer)
        observers.add(wrapper)
        super.observeForever(wrapper)
    }

    @MainThread
    override fun removeObserver(observer: Observer<in T>) {
        if ((observer is OneTimeObserver && observers.remove(observer)) || observers.removeIf { it.observer == observer }) {
            super.removeObserver(observer)
        }
    }

    @MainThread
    override fun setValue(t: T?) {
        observers.forEach { it.newValue() }
        super.setValue(t)
    }

    private class OneTimeObserver<T>(val observer: Observer<T>) : Observer<T> {

        private var handled = AtomicBoolean(true)

        override fun onChanged(t: T?) {
            if (handled.compareAndSet(false, true)) observer.onChanged(t)
        }

        fun newValue() {
            handled.set(false)
        }
    }
}

And then instead of such code:

private val _loginAction = MutableLiveData<LoginAction>()
val loginActionLD: LiveData<LoginAction> = _loginAction

I have used this code:

private val _loginAction = LiveEvent<LoginAction>()
val loginActionLD: LiveData<LoginAction> = _loginAction