I'm writing an Android app that takes screen-touch MotionEvents and converts them into a StateFlow, which is then collected by a Jetpack Compose function that draws them on the screen.
The MotionEvent capture (based around Modifier.motionEventSpy) works as expected, but the resultant Flows frequently don't make it to the Compose function, causing errors, if they're too close together in time.
It happens when two MotionEvents fire almost simulaneously: MotionEvent.ACTION_DOWN and the first MotionEvent.ACTION_MOVE happen in very rapid succession, and perhaps 20 or 30 per cent of the time, the MotionEvent.ACTION_DOWN Flow emission is never collected by the Compose function.
It doesn't happen all the time, or even most of the time, but it happens enough to be a problem.
(Another example of Flow emits not being collected: I've also noticed that, when I have an event listener on exoPlayer, emiting Flows when the exoPlayer state changes, only the second exoPlayer state change ever gets collected if two state changes happen near-simultaneously. For instance, when a media file is played to the very end, exoPlayer's onIsPlayingChanged listener fires at the same time as exoPlayer's onPlaybackStateChanged. If I have both of those listeners emiting on the same StateFlow, only one of them will ever be collected.)
So I'm thinking Kotlin Flows use some sort of mutex lock to prevent two emits/value changes happening at the same time. Is that correct?
And if that's the case, is there anything I do to ensure every Flow.emit or Flow.value = change makes it to the other side??
Are some types of Flows better than others when it comes to concurrency handling?
That is the expected behaviour of state flow. Sadly, the documentation points it out only on the testing section:
https://developer.android.com/kotlin/flow/test#stateflows
Note that this stream of values is conflated, which means that if values are set in a StateFlow rapidly, collectors of that StateFlow are not guaranteed to receive all intermediate values, only the most recent one.
Can you try using flow { } (I inow is cumbersome)? Or maybe can you try with channels (yep you loose the flow advantages)?
Related
I was trying out different kinds of flows like flows with channel, sharedflows and stateflows. What I did was, suppose I have a MainActivity, inside it I have two buttons side by side at the top and below them a fragmentContainerView. Initially the fragmentContainerView doesn't have any fragment.
Now I have a viewModel where I am emitting a range of int values in a loop with 1 or 2 seconds delay with all three flow types. And I have consumers of the values in MainActivity, fragmentA and fragmentB (fragmentB has collectLatest in all three flows when collecting). Clicking button1 attaches fragmentA and Button2 attaches fragmentB.
Now what happens after the values are started emitting suppose initially from 0. The mainActivity starts receiving as soon as the values are emitted. Then when I click button1, fragmentA starts receiving from initial value 0. After sometime I click button2 which removes fragmentA and attaches fragmentB, now fragmentB starts receing from value 0 which has collectLatest. Again if I click button1, fragmentA starts receiving from initial value 0.
I can understand that when the fragments are not visible they should not receive any values. But I want to understand is this the intended behaviour like whenever a new fragment is coming visible its receiving from initial value instead of having collectLatest which did not work. Am I doing anything wrong or why is it happening like this? Are the previous initial values stored in some form of cache? and if I somewhere want to get the current latest value when the view is visible, in what way can I do it? Guidance with some sample code will help. Thank you
Fixed the problem:
Actually I made a mistake by creating new instances of the viewModel in fragments, and it was the viewModel where the values were getting emitted. Fixed it by getting the MainActivity's viewModel instance everywhere.
Sounds like you are using cold flows instead of hot flows.
The behavior of cold flows is that each new collector gets values starting from the very beginning (the flow producer starts a new production process for each collector). For example, if you use the flow Flow builder, like this:
val flow = flow {
for (i in 1..3) {
emit(i)
delay(100)
}
}
Then each time a coroutine calls collect on it, that coroutine will get a fresh new stream of values, starting from the beginning of the above lambda function.
With a hot flow, the behavior depends on the implementation. Channel-based flows fan out, which means no two collectors will ever receive the same value. For each value emitted, only one collector will receive it. SharedFlows can have a buffer that replays up to a certain number of past values for every collector. A StateFlow behaves like a SharedFlow with replay value of 1. Each new collector can only collect the most recent value followed by any further latest values, and if it is slower at collecting than values are being produced, it will skip values.
The generally recommended type of flow to use in a ViewModel that fits most uses is a SharedFlow with a replay buffer of 1, and if based on an upstream flow using shareIn, a SharingStarted of WhileSubscribed(5000). This is a hot flow, but new subscribers get the most recently emitted value from the replay. So if the screen is rotated, the most recent value is still in memory and can be immediately displayed in the UI. The SharingStarted.WhileSubscribed(5000) allows it to stop collecting from the upstream flow when there are no more views on screen collecting from it, but the 5 second buffer waits to make sure it's not just a screen rotation causing a very temporary lack of subscribers.
I have this code in an activity SignInActivity:
signInButton.setOnClickListener{
val query: HashMap<String, String> = HashMap()
query["email"] = signInEmail.text.toString()
query["password"] = signInPassword.text.toString()
signInViewModel.getAuthToken(query)
signInViewModel.signInResponse.observe(this, {
response-> when(response){
is NetworkResult.Success ->{
response.data?.let { Toast.makeText(this, it.access, Toast.LENGTH_SHORT).show()}
}
is NetworkResult.Error ->{
Toast.makeText(this, response.message.toString(), Toast.LENGTH_SHORT).show()
}
is NetworkResult.Loading -> {
}
}
})
}
Let's suppose in the first try I wrote my password wrong and it only runs once, but then after that if I click it again it runs multiple time by creating multiple toasts in this example.
Like #gpunto says, you're adding a new Observer every click, so they're stacking up and each one fires when the LiveData updates.
But really, the observer doesn't have anything to do with the actual click anyway, it just receives updates to signInResponse and displays a thing. The click just calls getAuthToken with the current query. If doing that happens to cause a signInResponse update, then you have everything wired up to react to that event. But the Activity doesn't need to know how all that stuff works, or be written so one thing follows another.
That's a reactive pattern, where your UI is really just sending events (like getAuthToken when there's a click) and then reacting to other events so it can display them. By separating these things, you get a simple system that Just Works, and can react to updates no matter what caused them (e.g. a click, or restoring state) without having to write code to handle each case.
That said, this is a slightly tricky case because you have an event you want to consume. If you just set up that observer on signInResponse, it will fire every time you get a value for that LiveData. And that includes when the Activity is recreated (e.g. on rotation), observes the LiveData, and gets the current (last-set) value. Basically, if you show a Toast, the same Toast will appear every time the Activity is recreated. That would be fine for setting the current value on a TextView, but it's bad for a popup that should only appear once.
This is the current official recommendation for handling this situation. They're creating a UI state, which basically holds everything that needs to be displayed, including any popup messages (which acts like a queue, which is useful!). When the UI displays a message, it basically tells the ViewModel it's done so, and that handles removing the message from the state.
You could just implement this your own way, even if it's something simple like a clearResponse() function in your VM that clears the current value when you've seen it. It really depends on your app and what state you need to maintain. Here's some other examples from the Android devs - but like it says at the top, this advice is deprecated following the recommendations I linked earlier
I found a few articles talking about how RxJava/RxAndroid can replace event busses (such as otto)
https://lorentzos.com/rxjava-as-event-bus-the-right-way-10a36bdd49ba#.7a4619qva
https://medium.com/mobiwise-blog/use-rxjava-instead-of-event-bus-libraries-aa78b5023097#.ew28h2urf
A quote from the first article:
Otto from Square got officially deprecated the previous days. In the Android world we can cheer now something like “EventBusses are dead long live RxJava”.
There is one thing I am missing though:
One of the perks of event buses is that they help a lot with the Activity lifecycle in that you don't need to manage registering/unregistering to callbacks manually (and thus avoiding memory leaks easily)
Example flow:
Activity subscribes to an event for getting songs (say SongsAvailableEvent)
We request songs (we make a network request)
We change the device's orientation mid-request
The Activity dies and a new one is built, that is also subscribed to the SongsAvailableEvent
The new activity gets the event and updates the UI, and the old Activity (which is now dead) does not get the event (yay!)
The articles above make it look like this flow is "solved" by RxAndroid/RxJava, but using Rx you still need to subscribe/unsubscribe on an Observable manually when you change the device's orientation. Moreover, if I want to "reuse" the request made in an Observable, I need to somehow persist it so that I will subscribe on that same Observable in the new Activity (I'm not quite sure how to do that, but it is not the point :) ).
My question is: is this problem easily solvable with pure RxAndroid/RxJava, or do I still need to use Rx with an event bus / extend Rx using something like RxLifecycle (which complicates things since I do not manage my Observables in the presentation layer)?
Your Activity's onDestroy can always call unsubscribe.
As for making things work to reuse request- Look into Loaders and LoaderManager. EventBus and RxJava to solve that was never needed.
I would venture to say that there isn't any way out of the fact that at some point in the chain, the Observable has to be tied to the lifecycle of some Android platform object, such as an Activity. Also, because you have not mentioned it as a partial solution, I assume you are avoiding using retained Fragments. If you are creating and holding a reference to the Observable only within your Activity, it is not possible for the results of a request in-flight to survive destruction of the Activity and be automatically subscribed to the new one. In addition, at some point, either during an orientation change, or the Activity finishing in the middle of a network request, your Observable will leak a reference to the Activity (via its subscribe() callback) if it is not unsubscribed on the Activity's onDestroy().
I have found RxLifecycle to be simple to use. My base Activity class has a method on it:
public <T> Observable.Transformer<T,T> bindLifecycleOnMainThread() {
return o -> o.compose(lifecycleProvider.bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread());
}
lifecycleProvider is created as per the instructions for RxLifecycle, depending on how you create your provider. This particular implementation uses bindToLifecycle() rather than specifying an explicit lifecycle event, so its use is contextual. Calling it during onResume will cause it to end on onPause. Calling it during onStart will cause it to end on onStop. Calling it other other times will cause it to end on onDestroy. Since this subscription will be updating the UI, it must only be observed on the UI thread.
This can then then used in the Activity as follows:
yourObservable.compose(bindLifecycleOnMainThread())
.subscribe(event -> handleEvent(event));
Now, where does this observable come from? Well, there's still no magic, and if you want an Observable to have a longer lifespan than the Activity, that means the Observable must be held by a component that lives longer than the Activity. There are many, many ways to do this, but your particular use case maps well to the new ViewModel library included in the Android Architecture framework. If you were to use ViewModels, your ViewModel would have a method that begins the network request, and would have a PublishSubject or PublishRelay that would emit SongsAvailableEvent objects (though I recommend exposing it to your Activity as only an Observable<SongsAvailableEvent>, not a Subject, for good encapsulation!). Your ViewModel would make the network call and forward the results to your Subject.
Finally, your Activity, when created, will immediately get its ViewModel from the ViewModel registry and subscribe to the Observable<SongsAvailableEvent> (which is a Subject/Relay) exposed by the ViewModel, and then bind it to the Activity's lifecycle, as in the example above. The ViewModel will survive any orientation changes of the Activity, and therefore so will the observable. The Observable will then never attempt to deliver an event to a destroyed Activity and the new Activity will immediately begin listening for events.
I believe this strategy promotes good encapsulation, since the Activity does not concern itself with how the network request gets made, and does not concern itself with how the source Observable is created. The only way that the Activity manipulates the Observable is by choosing what happens when it receives an event, and binding the subscription to the lifecycle of the Activity.
This can be endlessly tweaked and refined by composing your Observables but this should get you on the way.
What is the equivalent of getStickyEvent() from EventBus in RxJava.
I would like to subscribe to observables from "screens" that are not in
the foreground/not active, but at anytime very well may pop in.
If events are continuously happening, I want these "screens" to receive them the next time
they are active/in the foreground.
Edit:
It sounds like I should have a replaySubject, and then when the "Screen" comes
to the foreground subcribe to it.....?
BehaviorSubject – emits the last emitted item when subscribed to,
then continues to emit items from the source observable
You already gave the answer yourself but just to confirm: Yes, you would use either BehaviorSubject or ReplaySubject.
After a new subscriber subscribes, they will both emit to that subscriber all items they receive from then onwards. However, each has a little extra beyond that:
BehaviorSubject will always start that sequence by immediately emitting the (one) most recent of the items it has received before the subscriber subscribed, if there was any. If there was none it will emit a default item if one was provided when it was created.
ReplaySubject will always start that sequence by immediately emitting (some or) all of the items it has recevied since its creation in the order that it received it. ReplaySubject can be initialized to limit the number of items it keeps in the cache for later subsribers, or to limit the amount of time that it will keep items in the cache. But (as far as I know), you cannot provide a default value if using a ReplaySubject.
Then, calling
subject.subscribe(new Subscriber<YourEventClass>() {
// implement Subscriber methods here
});
would be more or less equivalent to:
eventbus.registerSticky(this);
and having this implement the callbacks for the EventBus.
Synchronous vs Asynchronous
Note, though, that subscribing like this still makes the delivery of items from the subject asynchronous (like register/registerSticky), as you are in both cases only handing over some callback methods and are not waiting right there for the result to be returned.
I have not used the greenrobot EventBus myself but it seems that getStickyEvent() is synchronous/blocking.
If you want blocking behavior you would have to - instead of subscribing to it - convert the subject to a blocking observable (with subject.toBlocking()).
See here for more on blocking observables:
https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators
but basically you can then transform them to an iterable, or just get the latest item, or a number of other things.
I'm trying to create a turn based game using a 1v1 battle for android. My basic game loop checks if the two fighters are dead, if not then checks who is to go next. If its the player's turn then it should wait for an attack button to be clicked. If its the computer's turn, then it will execute a random attack. I'm having trouble getting the program to wait for the user input. I tried setting the button listener here but that's not doing it.
[edit] The determination for which character goes is based on a recovery integer. Each attack has a recovery value (50-100) which is added to the character's recovery. The nextMove() method checks to see which is closer to 0 and subtracts the difference from both characters. This allows the game to require more strategy because you don't attack just once a turn.
What can I do to get the game to pause at that point
Here's the code
public void battle(){
boolean playerGo;
while(!checkDead()){
playerGo=nextMove(); //returns true if its the players turn to go
if(playerGo){
//The game should wait here for the user input
moveButton1.setOnClickListener(this);
}
else{
randomMove(); //game automatically goes
}
}
}
When your app starts up, there's one thread on which everything runs, including event handlers. After you do your setup and call battle(), that thread is sitting there going around and around the loop. It's so busy going around and around the loop that it doesn't notice that there's a click event waiting to be processed!
There's a few options:
Restructure your code. It looks like the basic structure is that the player moves, then the game moves. You could remove this loop entirely, and instead call randomMove() after each time you handle the player's move. Handle the player's move in the OnClickListener for moveButton1. That way everything just happens on events. This would be simpler overall, and is probably the Right Thing to do.
Make the smallest possible change to your code to get it working. This would probably mean pulling the contents of your while loop into a Runnable, which you schedule by calling Handler.post. The first line calls checkDead and returns if true. The last line reschedules the Runnable. In between is the body of the while loop. The effect of this is that your loop body runs, then the event handler gets a turn, then your loop body runs, then the event handler runs. This is probably a bad idea.
Run battle() in another thread. This is probably a bad idea.
Why are 2. and 3. bad ideas? On a mobile device, battery life is precious, and running a check to see if you need to do something over and over again will keep the CPU busy chewing up battery life. Much better to sit there idle until you need to do something - this is what option 1 achieves.
So if 2. and 3. are bad ideas, why mention them? Welllllll, 2. I mention because it's the closest thing I've got to an answer to the question you actually asked. I mention 3. because there's a sense in which your current code is a fairly clear embodiment of the game logic. You could rework it so it runs in a separate thread, and instead of nextMove() returning true, nextMove() waits until the player makes a move (this would involve semaphores or mutexes or promises). But this would be an explicitly multi-threaded program, and as such would be difficult to write correctly. I recommend you don't attempt it at this stage in your programming career - the most likely outcome is a program that stops and waits forever, or that corrupts its data structures, in a way that is exceedingly difficult to diagnose.
Button.SetOnClickListener() function will be triggered, only when the user clicks on the button. As such it doesn't wait\block till the user input. This is by design in Android, that you cannot have a blocking window waiting for user input. Instead change your design to display hint saying 'now its user's move'.
User does first move by clicking the button.
SetOnclickListener() will be invoked. Have the user action code inside it.
Towards end of SetOnclickListener() have the computer action code.
With this cycle you can have user move and computer move chained.