Channel is not trigger in ComponentActivity - android

I want to trigger an event that I defined in my base viewmodel and observe it my MainActivity. I defined channel like below
private val message = Channel<String>()
val messageFlow = message.receiveAsFlow()
Also for trying send message to channel I wrote a function like below
fun triggerEvent() = viewModelScope.launch {
message.send("MESAJ.Merhaba.BaseViewModel")
}
But when I call this function in a viewmodel that based BaseViewModel, I can't observe the channel.In my MainActivity (extends ComponenctActivity and single activity) onCreate
lifecycleScope.launchWhenStarted {
viewModel.messageFlow.collect {
Log.d("MESAJ","Mesaj geldi!!!")
}
}
I am collecting the channel in hot. But this part is not trigger. What is the problem?

Related

liveData with coroutines only trigger first time

I have a usecase:
Open app + disable network -> display error
Exit app, then enable network, then open app again
Expected: app load data
Actual: app display error that meaning state error cached, liveData is not emit
Repository class
class CategoryRepository(
private val api: ApiService,
private val dao: CategoryDao
) {
val categories: LiveData<Resource<List<Category>>> = liveData {
emit(Resource.loading(null))
try {
val data = api.getCategories().result
dao.insert(data)
emit(Resource.success(data))
} catch (e: Exception) {
val data = dao.getCategories().value
if (!data.isNullOrEmpty()) {
emit(Resource.success(data))
} else {
val ex = handleException(e)
emit(Resource.error(ex, null))
}
}
}
}
ViewModel class
class CategoryListViewModel(
private val repository: CategoryRepository
): ViewModel() {
val categories = repository.categories
}
Fragment class where LiveDate obsever
viewModel.apply {
categories.observe(viewLifecycleOwner, Observer {
// live data only trigger first time, when exit app then open again, live data not trigger
})
}
can you help me explain why live data not trigger in this usecase and how to fix? Thankyou so much
Update
I have resolved the above problem by replace val categories by func categories() at repository class. However, I don't understand and can't explain why it works properly with func but not val.
Why does this happen? This happens because your ViewModel has not been killed yet. The ViewModel on cleared() is called when the Fragment is destroyed. In your case your app is not killed and LiveData would just emit the latest event already set. I don't think this is a case to use liveData builder. Just execute the method in the ViewModel when your Fragment gets in onResume():
override fun onResume(){
viewModel.checkData()
super.onResume()
}
// in the viewmodel
fun checkData(){
_yourMutableLiveData.value = Resource.loading(null)
try {
val data = repository.getCategories()
repository.insert(data)
_yourMutableLiveData.value = Resource.success(data)
} catch (e: Exception) {
val data = repository.getCategories()
if (!data.isNullOrEmpty()) {
_yourMutableLiveData.value = Resource.success(data)
} else {
val ex = handleException(e)
_yourMutableLiveData.value = Resource.error(ex,null)
}
}
}
Not sure if that would work, but you can try to add the listener directly in onResume() but careful with the instantiation of the ViewModel.
Small advice, if you don't need a value like in Resource.loading(null) just use a sealed class with object
UPDATE
Regarding your question that you ask why it works with a function and not with a variable, if you call that method in onResume it will get executed again. That's the difference. Check the Fragment or Activity lifecycle before jumping to the ViewModel stuff.

how to stop observer to automatically observe data when pop back to previous fragment? [duplicate]

I have some problem in nested fragment in Kotlin. I have nested fragment with ViewModel. After resuming fragment from back button press all observers on viewModel LiveData triggers again although my data does not changed.
First i googled and tried for define observer in filed variable and check if it is initialized then do not observer it again:
lateinit var observer: Observer
fun method(){
if (::observer.isInitialized) return
observer = Observer{ ... }
viewModel.x_live_data.observe(viewLifecycleOwner ,observer)
}
So at first enter to fragment it works fine and also after resume it does not trigger again without data change but it does not trigger also on data change!
What is going on?
LiveData always stores the last value and sends it to each Observer that is registered. That way all Observers have the latest state.
As you're using viewLifecycleOwner, your previous Observer has been destroyed, so registering a new Observer is absolutely the correct thing to do - you need the new Observer and its existing state to populate the new views that are created after you go back to the Fragment (since the original Views are destroyed when the Fragment is put on the back stack).
If you're attempting to use LiveData for events (i.e., values that should only be processed once), LiveData isn't the best API for that as you must create an event wrapper or something similar to ensure that it is only processed once.
After knowing what happen I decide to go with customized live data to trigger just once. ConsumableLiveData. So I will put answer here may help others.
class ConsumableLiveData<T>(var consume: Boolean = false) : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(
owner,
Observer<T> {
if (consume) {
if (pending.compareAndSet(true, false)) observer.onChanged(it)
} else {
observer.onChanged(it)
}
}
)
}
override fun setValue(value: T) {
pending.set(true)
super.setValue(value)
}
}
And for usage just put as bellow. It will trigger just once after any update value. This will great to handle navigation or listen to click or any interaction from user. Because just trigger once!
//In viewModel
val goToCreditCardLiveData = ConsumableLiveData<Boolean>(true)
And in fragment:
viewModel.goToCreditCardLiveData.observe(viewLifecycleOwner) {
findNavController().navigate(...)
}
If u are using kotlin and for only one time trigger of data/event use MutableSharedFlow
example:
private val data = MutableSharedFlow<String>() // init
data.emit("hello world) // set value
lifecycleScope.launchWhenStarted {
data.collectLatest { } // value only collect once unless a new trigger come
}
MutableSharedFlow won't trigger for orientation changes or come back to the previous fragment etc

Kotlin channel not ready to send events after put app in background

Kotlin channel stops being able to send events after putting app in background (don't keep activities enabled)
class UserRepositoryImpl(
private val userRequestDataSource: UserRequestDataSourceContract,
) : UserRepositoryContract {
private var loginToken: LoginTokenDecode? = null
private val authChannel by lazy { Channel<Boolean?>() }
override suspend fun requestLogin(userLoginBo: UserLoginRequestBo){
// isClosedForSend is true after putting app in background
if(!authChannel.isClosedForSend) {
authChannel.send(true)
}
}
Viewmodel
class UserViewModel : ViewModel {
init {
authChannelUc.invoke(scope = viewModelScope, onResult = ::authenticated)
}
...
}
Based on your comment that you are using viewModelScope; and the fact that you have "Do not keep activities in background" enabled on device - The Activity is killed going to background, which cancels the viewModelScope, which auto-closes the channel.
On the consumer side, use for:
for (token in authChannel) {
withContext(dispatcherForLaunch) {
onResult(isTokenValid(loginTokenDecode))
}
}
instead authChannel.consumerEach())

how to observe a liveData only for the new update after the subscription to the liveData

using PagedList, and here it does not have database back, but a data list (call it CachedDataList) in the memory which could be filled in by fetchMore() function.
Having the PositionalDataSource, DataSource.Factory and PagedList.BoundaryCallback, it works but one issue here.
The flow is the PositionalDataSource's loadInitial() will be called at beginning to start to load data from the CachedDataList, and call loadRange() after that to continue loading data from the CachedDataList by page size.
When all data from the CachedDataList are paged off, the BoundaryCallback::onItemAtEndLoaded() will be called (if there is no backing data at beginning then the BoundaryCallback::onZeroItemsLoaded() is called),
In there it will start to asking fetchMore to append more data into the CachedDataList, and when the new data is appended to it then call the DataSource's invalidate() to restart the new PagedList and DataSource pair, and starting from PositionalDataSource's loadInitial() again.
It is done by
observableCachedData.observe(owner, refreshHandler!!)
//??? TODO: how to only listen to newly posted data after
//starting the observe?
//DOC: 'If LiveData already has data set, it will be delivered to the observer.'
// fetchMore
val didFetch = dataRequester.fetchMore() //asyc call
here, it observes the observableCachedData's change, and if there is change then the onChanged() of the
class RefreshHandler(val observableCachedData: MutableLiveData<List<Data>>) : Observer<List<Data>> {
override fun onChanged(datalist: List<Data>?)
will be called, and in which to call the DataSource's invalidate()
but the subscription of observableCachedData.observe() causes the refreshHandler called immediately (it's by design as stated in the DOC), this behavior is not desired here since we want the handler is called when the new data is append to the CachedDataList.
i.e. the CachedDataList had 30 data, when do fetchMore() there will be another 30 data appended to it, become 60. But this onChange() is called with data still at 30 (the append has not been coming yet).
Is there a way to subscribe to a live data but only get notified for update that happened after it is subscribed to it?
class DataBoundaryCallback(
private val owner: LifecycleOwner,
private val dataRequester: FetchMoreRequester,
private val dataSourceFactory: DataSourceFactory?
) : PagedList.BoundaryCallback<IData>() {
private var hasRequestInProgress = false
override fun onZeroItemsLoaded() {
requestAndSaveData()
}
override fun onItemAtEndLoaded(itemAtEnd: IData) {
requestAndSaveData()
}
private fun requestAndSaveData() {
if (hasRequestInProgress) return
hasRequestInProgress = true
// ask dataRequester to fetchMore
// setup observer
val cachedDataList = dataRequester.getCachedLiveData()
val observableCachedData = cachedDataList.getLiveData()
refreshHandler = RefreshHandler(observableCachedData)
observableCachedData.observe(owner, refreshHandler!!) //??? TODO: how to only listen to newly posted data after starting the observe? DOC: 'If LiveData already has data set, it will be delivered to the observer.'
// fetchMore
val didFetch = dataRequester.fetchMore()
if (!didFetch) { //not stated fetch
hasRequestInProgress = false
observableCachedData.removeObserver(refreshHandler!!)
}
}
var refreshHandler : RefreshHandler? = null
inner class RefreshHandler(val observableCachedData: MutableLiveData<List<Data>>) : Observer<List<Data>> {
override fun onChanged(datalist: List<Data>?) {
observableCachedData.removeObserver(refreshHandler!!)
val dataSource = dataSourceFactory.getPositionalDataSource()
// to start a new PagedList and DataSource pair flow
// and trigger the DataSource's loadInitial()
dataSource.invalidate()
}
}
}
You can use SingleLiveEvent
SingleLiveEvent
made it work with checking the emitted data list in the RefreshHandler(val observableCachedData: MutableLiveData<List<Data>>), only invalidate if there is more added to the list.
(it should look for a better way to determine the datalist has truly increased).
inner class RefreshHandler(val observableCachedData: MutableLiveData<List<Data>>) : Observer<List<Data>> {
val oldData = observableCachedData.value
override fun onChanged(datalist: List<Data>?) {
if (datalist.size == oldData.size) {
// is from previous cached data in the liveData
return
}
observableCachedData.removeObserver(refreshHandler!!)
val dataSource = dataSourceFactory.getPositionalDataSource()
// to start a new PagedList and DataSource pair flow
// and trigger the DataSource's loadInitial()
dataSource.invalidate()
}
}

Use LiveData without Lifecycle Owner

I could not find any information, if it's a bad idea to use LiveData without a lifecycle owner. And if it is, what could be the alternative?
Let me give you just a simple example
class Item() {
private lateinit var property: MutableLiveData<Boolean>
init {
property.value = false
}
fun getProperty(): LiveData<Boolean> = property
fun toggleProperty() {
property.value = when (property.value) {
false -> true
else -> false
}
}
}
class ItemHolder {
private val item = Item()
private lateinit var observer: Observer<Boolean>
fun init() {
observer = Observer<Boolean> { item ->
updateView(item)
}
item.getProperty().observeForever(observer)
}
fun destroy() {
item.getProperty().removeObserver(observer)
}
fun clickOnButton() {
item.toggleProperty();
}
private fun updateView(item: Boolean?) {
// do something
}
}
You can register an observer without an associated LifecycleOwner object using the
observeForever(Observer) method
like that:
orderRepo.getServices().observeForever(new Observer<List<Order>>() {
#Override
public void onChanged(List<Order> orders) {
//
}
});
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.
Ref
https://developer.android.com/topic/libraries/architecture/livedata.html#work_livedata
For me LiveData has two benefits:
It aware of life cycle events and will deliver updates only in an appropriate state of a subscriber (Activity/Fragment).
It holds the last posted value, and updates with it new subscribers.
As already been said, if you're using it out of the life cycle components (Activity/Fragment) and the delivered update could be managed anytime, then you can use it without life cycle holder, otherwise, sooner or later, it may result in a crash, or data loss.
As an alternative to the LiveData behavior, I can suggest a BehaviourSubject from RxJava2 framework, which acts almost the same, holding the last updated value, and updating with it new subscribers.

Categories

Resources