I need to stop casting after the user enabled it inside my app.
The app listened to MediaRouter.Callback as below:
private val mediaRouterCallback = object : MediaRouter.Callback() {
override fun onRouteChanged(router: MediaRouter?, route: RouteInfo?) {
super.onRouteChanged(router, route)
// notify observers that casting occurs
}
}
Now, in the observers, I need to stop casting immediately when the user should not use casting inside the app.
Let's suppose the code below is inside a Fragment and observes the casting event, so what would be the implementation code for stopCasting() method, for example:
when (event) {
PreventCasting -> {
stopCasting()
}
}
Related
I need to trigger a fake event to process the current progress with a flow and update the UI.
I've got following function which belongs to ViewModel A:
val listener = object : Player.Listener {
override fun onEvents(player: Player, events: Player.Events) {
trySend(player.toPlayerState())
.onFailure { Timber.w(it, "Error sending player state") }
super.onEvents(player, events)
}
}
This listener gets called on every event from the player (mediaController). All right!
I've got a second ViewModel (ViewModel B) which reseaves a message from the UI progress bar:
binding.playerControlView.setProgressUpdateListener { position, _ ->
viewModel.onUpdateProgress(position)
}
If the onUpdateProgress function calls eg. the function to increase the sound, the event listener gets triggered. All right!
My goal is to trigger the event listener without changing anything (kind of fake trigger).
This is the code which should trigger the event listener:
fun onUpdateProgress(position: Long) {
viewModelScope.launch {
Timber.d("YYYYYY $position")
val player = getMediaController() // Returns Deferred<MediaController>
player.todo() // Replace by correct function
}
}
Maybe there is also a better approach to update the progress UI in ViewModel A?
I have a class which monitors the network state.
I'm using Flow to be able to collect the network state updates.
However, I also need to leave an option to use manual listeners that programmers can "hook" onto to be able to receive the network changes.
My code is simple :
private val networkTypeState = MutableStateFlow<NetworkState>(NetworkState.Unknown)
val networkTypeAsFlow: StateFlow<NetworkState> by notifyDelegate(networkTypeState)
private fun <T> notifyDelegate(init: T) =
Delegates.observable(init) { prop, _, new ->
Lg.i("notify subscribers of network update: ${prop.name} = $new")
notifySubscribers()
}
sealed class NetworkState {
object Unknown: NetworkState()
object Disconnected: NetworkState()
data class Connected(val isCellularOn: Boolean, val isWifiOn: Boolean): NetworkState()
}
Then when I update the state,
for example :
networkTypeState.value = NetworkState.Disconnected ,
the delegates.observable does not get called.
Worth noting, when I use networkTypeAsFlow.collect { .. } , this works well, meaning - the networkTypeAsFlow does get updated, it just doesn't call the delegates.observable
The Observable delegate monitors changes to the property itself. It is futile to use Observable for a val property, because a val property is never set to a new value. Mutating the object pointed at by the property is completely invisible to the property delegate.
If you want to observe changes, you can launch and collect:
private val networkTypeState = MutableStateFlow<NetworkState>(NetworkState.Unknown)
val networkTypeAsFlow: StateFlow<NetworkState> = networkTypeState
init {
viewModelScope.launch {
networkTypeAsFlow.collect {
Lg.i("notify subscribers of network update: $it")
notifySubscribers()
}
}
}
An additional benefit here is that notifySubscribers will always be called from the same dispatcher, regardless of which thread the network state was changed from.
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
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.
I am just getting into unit testing on Android and I ran into some difficulties when I tried to test bindTo() function of this class.
class DataFlow<T> (produce: DataFlowProducer<T>): BaseDataFlow<T>(produce) {
var updateOnAttach: Boolean = true
fun bindTo(viewKontroller: ViewKontroller, updateImmediately: Boolean, updateUi: (data: T) -> Unit) {
this.updateUi = updateUi
if (updateImmediately)
flow()
viewKontroller.addLifecycleListener(object : Controller.LifecycleListener() {
override fun postAttach(controller: Controller, view: View) {
if (updateOnAttach) flow()
}
override fun preDestroyView(controller: Controller, view: View) {
viewKontroller.removeLifecycleListener(this)
this#DataFlow.updateUi = null
}
})
}
}
If I mock my ViewKontroller test still crashes with NPE on line viewKontroller.addLifecycleListener.
So what am I doing wrong?
What you want to check in the test is probably at least this:
LifecycleListener added to ViewKontroller
When onPostAttach is called by ViewKontroller something happens
When preDestroyView is called by ViewKontroller something else happens
So, the test double of ViewKontroller that you pass into constructor needs to "tell" you whether a listener was registered, and also to delegate method calls to that listener.
In such cases, when the test double object needs to have some actual functionality, it is best to implement a fake than using a mock.
In your case, just implement FakeViewKontroller that extends ViewKontroller and pass it to system under test instead of mock.
In that class you can expose additional methods that will allow you to ensure that LifecycleListener was added, and call methods on that listener in test cases.