Viewmodel preserving data after finishing activity - android

I have a MainActivity form that I am opening CreatePassword Activity in that, I am saving password and finish CreatePasswordActivity with sending Intent back to MainActivity.
Like MainActivity -----> CreatePassword(Finish) ---Intent----> MainActivity
private fun observeIntentResult() {
generatePasswordViewModel.getIntentResult().observe(this#CreatePasswordActivity, androidx.lifecycle.Observer { intent ->
Toast.makeText(this, "Got Same Data", Toast.LENGTH_SHORT).show()
setResult(Activity.RESULT_OK, intent)
finish()
})
}
But Now when I open CreatePasswordActivity again from MainActivity, it's LiveData automatically sending previous data (intent) and CreatePasswordActivity is suddenly finished.
Do I have any mistake in implementing code? Anybody have idea how to resolve this?

Finally I have used below class, which helped me to resolve current issue, will see and update answer in future if I will be able to find any better solution.
open class VolatileLiveData<T> : MutableLiveData<T>() {
private val lastValueSeq = AtomicInteger(0)
private val wrappers = HashMap<Observer<in T>, Observer<T>>()
#MainThread
public override fun setValue(value: T) {
lastValueSeq.incrementAndGet()
super.setValue(value)
}
#MainThread
public override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observe(owner, observerWrapper)
}
#MainThread
public override fun observeForever(observer: Observer<in T>) {
val observerWrapper = ObserverWrapper(lastValueSeq, observer)
wrappers[observer] = observerWrapper
super.observeForever(observerWrapper)
}
#MainThread
public override fun removeObserver(observer: Observer<in T>) {
val observerWrapper = wrappers[observer]
observerWrapper?.let {
wrappers.remove(observerWrapper)
super.removeObserver(observerWrapper)
}
}
}
private class ObserverWrapper<T>(private var currentSeq: AtomicInteger, private val observer: Observer<in T>) : Observer<T> {
private val initialSeq = currentSeq.get()
private var _observer: Observer<in T> = Observer {
if (currentSeq.get() != initialSeq) {
// Optimization: this wrapper implementation is only needed in the beginning.
// Once a valid call is made (i.e. with a different concurrent sequence), we
// get rid of it any apply the real implementation as a direct callthrough.
_observer = observer
_observer.onChanged(it)
}
}
override fun onChanged(value: T) {
_observer.onChanged(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()
})

Avoid LiveData triggered twice

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
}
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().
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

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>()

Navigation Component - Problem with Livedata lifecycle

I'm beginning with navigation components and I'm facing some problem with a livedata observer.
For example:
I have this livedata, who manage auth response from server.
viewModel.authenticate.observe(this, Observer {
manageAuthResponse(it)
})
Everything works fine, and I go to Fragment B.
But when I'm in Fragment B, and I try to go back to Fragment A (who contains that livedata), the Observer fires again with the previous result (SUCCESS).
How can I prevent this?
When I go back, I want to refresh this result and prevent livedata observer to be fired.
Wrap your LiveData object in a ConsumableValue like this
class ConsumableValue<T>(private val data: T) {
private var consumed = false
fun consume(block: ConsumableValue<T>.(T) -> Unit) {
if (!consumed) {
consumed = true
block(data)
}
}
}
then in viewmodel
val authenticate = MutableLiveData<Consumable<AuthenticationObject>>()
and in your fragment
viewModel.authenticate.observe(this, Observer { consumable ->
consumable.consume {
manageAuthResponse(it)
}
})
Wrap the LiveDate Like this
open class LiveEvent<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 observeForever(observer: Observer<in T>) {
val wrapper = ObserverWrapper(observer)
observers.add(wrapper)
super.observeForever(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
}
}
}
then in ViewModel
val viewModel = LiveEvent<Resource<String>>()
This solution is work for me
You can check out the code in this github

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