override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
val status = operation() // operation takes time
return status
}
return super.onKeyDown(keyCode, event)
}
When an event occurs, the above handler gets called. Now if it requires time to decide whether to pass the true or false status to next layers (super) inside if block, how can the flow be designed properly. I am required to get the result asynchronously because the time to decide return value (i.e. true or false) might be longer and the function shouldn't keep main thread await. So, I need to find other way to make the super call delayed.
What is the proper way to get rid of this problem? Any specific design pattern to deal this kind of problem?
Please ignore the language.
Update 1
I have been thinking to store keyCode and event and return true (means the event was consumed and no need to re consume by other observer) immediately, and then after operation() is completed i have the status available and now I can re trigger the pending stored event with same keyCode and event. But not all event provides functions to trigger manually. How can I do so for events that can not be triggered manually.
My proposed solution
private fun doOperation(callback: (status:Boolean) -> Unit) {
Handler().postDelayed({
callback.invoke( arrayOf(true, false).random() )
}, 5000)
}
var pendingEvent: KeyEvent? = null
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
doOperation {
if (it && pendingEvent != null){
dispatchKeyEvent(pendingEvent)
pendingEvent = null
} else {
// do nothing
}
}
return true // let other know I consumed it
}
return super.onKeyDown(keyCode, event)
}
Is this a proper way? What can be bad of this thought?
Because events are fire-and-forget (no return value or void) it should be clear that asynchronous events or event handlers that return a value are a contradiction in itself or a paradox. When asynchronous means "waiting without blocking" and event "notify without waiting" you are obviously creating more problems than solutions. A return value also implies that the caller is waiting for completion of the operation and is interested in the result.
I recommend to restructure your application.
An event handler should never return a value or be asynchronous.
what if the observer needs more time to decide if it consumes or not?
How do you deal it?
This decision (or decisions in general) depends always on the state of at least one variable.
There are two situations
the state is known at the moment the observer gets notified or
the state is unknown at this moment (the observer needs more time).
Situation 1) requires no waiting, but situation 2) does.
In case of situation 2), the change of the state is always triggered by an operation. The execution duration of this operation determines how long the waiting time is. This operation must raise an event, when the relevant state has changed.
In general you have three options to wait:
Keep spinning until a condition is met like polling (e.g. infinite
loop): while(true){}.
Use a timer and after the elapsed time do the action
Use events whenever you need to wait.
The first two options will block a thread. If the thread is the same as the observable thread, than you are blocking the observable and all other waiting observers too. If that thread is an UI thread, then the UI will stall and become unresponsive. Events are a pattern that will solve the blocking issue.
Let's imagine the following scenario: you want to start a specific animation. You have two constraints: the type of the animation depends on which a key was pressed AND before you can start a new animation you have to wait until the first one completes. E.g. when TAB was pressed move a rectangle from left to right. When ENTER was pressed move a rectangle from top to bottom.
This introduces two waiting situations: key pressed and animation completed. To handle the waiting, you would create and associate an event for each potential waiting situation: keyPressed and animationStopped event:
The keyboard key pressed event
The interface to be implemented by the observer that is waiting for a particular key to be pressed:
interface IKeyPressedListener {
void onKeyPressed(int keyCode);
}
The event interface to be implemented by the observable that exposes and raises the event:
interface IKeyPressedEvent {
void subscribeToKeyPressedEvent(IKeyPressedListener listener);
void unsubscribeToKeyPressedEvent(IKeyPressedListener listener);
}
The animation event
The interface to be implemented by the observer that is waiting for an animation to stop:
interface IAnimationStoppedListener {
void onAnimationStopped();
}
The event interface to be implemented by the observable that exposes and raises the event:
interface IAnimationStoppedEvent {
void subscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
void unsubscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
}
The actual event listener
The implementation of the class that plays an animation on key pressed:
class AnimationController implements IKeyPressedListener, IAnimationStoppedListener
{
// store the key that was pressed,
// so that an event that will be raised at a later can process it
private int keyCodeOfLastKeyPressed = 0;
// The reference to the class that exposes
// the keyPressedEvent by implementing IKeyPressedEvent
KeyboardController keyboardController;
// The reference to the class that exposes
// the animationStoppedEvent by implementing IAnimationStoppedEvent
AnimationPlayer animationPlayer;
// Constructor
public AnimationController() {
this.keyboardController = new KeyboardController();
this.animationPlayer = new AnimationPlayer();
// Subscribe to the key pressed event
this.keyboardController.subscribeToKeyPressedEvent(this);
}
#Override
public void onKeyPressed(int keyCode) {
if (this.animationPlayer.hasPlayingAnimation) {
// Instead of waiting that the animation completes
// subscribe to an event and store the relevant data
this.keyCodeOfLastKeyPressed = keyCode;
this.animationPlayer.subscribeToAnimationStoppedEvent(this);
}
else {
// There is no playing animation, so no need to wait
this.animationPlayer.playAnimation(keyCode);
}
}
// After a while this handler will be invoked by the event source.
#Override
public void onAnimationStopped() {
// To avoid memory leaks unsubscribe first
this.animationPlayer.unsubscribeToAnimationStoppedEvent(this);
// Since we stored the key code earlier, we can continue to process it
// and start a new animation that maps to a specific key
this.animationPlayer.playAnimation(this.keyCodeOfLastKeyPressed);
}
}
Following the Observer Pattern avoids thread blocking waiting time. The application can just leave the context and return when the event occurred (in this case the AnimationStopped event). To store the change value (event args) of an event, a private shared field is introduced, so that the second event handler can access and finally process it.
Using Observer-pattern may help you.
You can use Debounce operator (debounce(DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)) to delay the event.
only emit an item from an Observable if a particular timespan has passed without it emitting another item
Check the official documentation for how to use
Edit 1
Code snippet
RxView.clicks(mButton)
.debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(...)
Related
I have managed to implement the SwipeRefreshLayout in my project, and it works as expected, executing the callback code, displaying the spinner, and reloading the relevant data. And it works time and again without issue.
However, the problem is, whenever I perform the pull-down gesture, the spinner is pulled down correctly, turning clockwise in sync with the pull gesture, but it immediately jumps back to the top once the touch is released, even before the callback process has completed. This is how it's been implemented:
tableRefresh = SwipeRefreshLayout(this)
tableRefresh?.setOnRefreshListener {
tableRefresh?.isRefreshing = true
swipeRefreshHandler()
}
// global Intent
var cloudService: Intent? = null
private fun swipeRefreshHandler() {
// starts a server-update service
startService(cloudService?.setAction(serverManualSync))
}
cloudServiceBroadcastReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
// server-update service notifies of completion
serverManualSyncComplete -> tableRefresh?.isRefreshing = false
// other cases
}
}
}
Again, there is no problem with the functionality per se, just that the spinner does not remain visible for the duration of the callback. And it works on every consecutive pull gesture.
What could I be doing wrong?
This is because you are setting the SwipeRefreshLayout to stop refreshing immediately.
The swipeRefreshHandler() method is called directly.
Instead, you should listen to when the callback process is completed then set the isRefreshing to false.
Could you show the relevant callback code? Perhaps it is running asynchronously?
Your code could look something like this:
tableRefresh = SwipeRefreshLayout(this)
tableRefresh?.setOnRefreshListener {
swipeRefreshHandler()
}
private fun swipeRefreshHandler(loadingState: Boolean = true) {
if(loadingState){
//
// code to reload the data...
//
}
sideMenuRefresh?.isRefreshing = loadingState
}
then in you async call (using coroutines or retrofit for example), you can call
swipeRefreshHandler() // loading state for ongoing operation
OR
swipeRefreshHandler(false) // when data or process completed (success/fail)
So I am trying to build a dialog fragment screen (Android) which has an EditText where users can input their desired username. Using the RxBindings library's RxTextView.textChanges() I am observing the EditText and doing the following:
Resetting existing hints and disabling the "Confirm" button.
Filtering out invalid strings using a regex pattern.
Making a request to the server to check if the valid string is available.
Showing the appropriate response and, if applicable, enabling the "Confirm" button.
My repository's checkUsername() method returns a Single<Boolean> to denote whether the username was available or not. Here's the code:
Observable<String> usernameObservable = RxTextView.textChanges(usernameEditText)
.doOnNext(charSequence -> resetUsernameChecks())
.filter(charSequence -> !TextUtils.isEmpty(charSequence))
.debounce(400, TimeUnit.MILLISECONDS)
.map(CharSequence::toString)
.filter(s -> {
boolean match = s.matches("^(?=.{4,12}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?<![_.])$");
if (!match) showMessage(R.string.profile_dialog_username_hint_invalid, true);
return match;
});
Disposable usernameSubscription = usernameObservable
.subscribe(username -> {
Disposable d = viewModel.checkUsername(username)
.subscribe(available -> {
if (available) {
claimButton.setEnabled(true);
showMessage(R.string.profile_dialog_username_hint_available, false);
} else {
showMessage(R.string.profile_dialog_username_hint_unavailable, true);
}
});
disposables.add(d);
});
disposables.add(usernameSubscription);
With the following helper methods:
#UiThread
private void resetUsernameChecks() {
claimButton.setEnabled(false);
usernameInputLayout.setError(null);
}
#UiThread
private void showMessage(#StringRes int message, boolean error) {
showMessage(getString(message), error);
}
#UiThread
private void showMessage(String message, boolean error) {
if (error) {
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_RedMessage);
} else {
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_GreenMessage);
}
usernameInputLayout.setError(message);
}
However, the issue I run into is that either of the showMessage() methods cause a crash because the there may either be a background thread in the running (thus preventing changes to the UI) or that a previously called checkUsername() lingers back and messes everything up.
Essentially, how can I make sure I do work on the correct thread and cancel any lingering network calls before making a new one?
EDIT:
I fixed the thread issue by encapsulating the UI methods inside a Runnable. However, I still have issue with cancelling previous call. Lets say the user searches for an available username -- nick -- but then quickly hits backspace. The already fired network call now comes back and inaccurately shows that nic is available even though it may not be.
So, for you to update UI, you gotta do it in UI Thread. I guess,
usernameInputLayout.setErrorTextAppearance(R.style.AppTheme_GreenMessage);
is like the UI thread thing, so you need to use subscribeOn.
If you would show more from the log, we would know the error. But, right now, your best option is to use UI Thread to update UI.
usernameObservable.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(v -> //whatever you want )
Edit: My bad about "Observable by default runs on background thread." It is wrong. The correct is: It runs in the thread it is defined in, unless we use subscribeOn()/observeOn() methods.
I'm having an issue trying to understand, in a reactive way, how simultaneous operations to the same observable should work.
The scenario is the following:
I have a list of users and a remove button.
Every time I press remove I'm making a call to the API: UsersApi.removeUser. It is possible to remove multiple users at the same time. Which means that multiple UsersApi.removeUser are happening simultaneously.
After each UsersApi.removeUser I need to make a UsersApi.refreshUser call
So in terms of pseudo code what I am doing when clicking remove is the following:
Presenter:
public Observable<User> removeUser(int userId) {
return UsersApi.removeUser(userId)
.flatMap(user -> UsersApi.refreshUser(userId));
}
Fragment:
public void removeUser() {
presenter.removeUser(userId)
.subscribe(user -> {
//remove user from ui
// update number of total users
})
}
The problem with this approach is that because of the asynchronous nature of the remove (multiple removes allowed) I cannot guarantee that what is reaching the subscribe is the latest one. The subscribe will be reached twice, one for each remove, and the user info might not be updated or the latest. Does that make sense?
What I want to happen:
Parallel/Simultaneous remove calls using a reactive approach (triggered by multiple remove clicks from the user)
After a remove call finishes, start the next remove call
Edit: What I would like to know is how to do/if is possible to do the solution I did (see edit2) using Rx operators.
Edit2: My solution for this was to enqueue the user operations (in this case remove) and emit, using a PublishSubject, when the UsersApi.refreshUser(userId) call finishes.
So basically what I did was (pseudo code):
private final PublishSubject<UserOperation> userOperationObs;
private final ConcurrentLinkedQueue<UserOperation> pendingOperations;
private boolean executingOperation;
private void emitUserOperation(final UserOperation operation) {
if (!executingOperation) {
executingOperation = true;
userOperationObs.onNext(operation);
} else {
executingOperation.add(operation);
}
}
public Observable<User> removeUser(UserOperation operation) {
return UsersApi.removeUser(operation.getUserId)
.switchMap(user -> UsersApi.refreshUser(operation.getUserId))
.doOnNext(user -> {
executingOperation = false;
final UserOperation nextOperation = pendingOperations.poll();
if (nextOperation != null) {
userOperationObs.onNext(operation);
}
};
}
You could turn your UI click into Observable (eg. by using RxBinding). After that, you could use concatMap operator to perform api call so it will start next network call once current api call is finished.
// emit clicks as stream
Observable<?> clicks = RxView.clicks(removeView)
// listen clicks then perform network call in sequence
clicks.concatMap(ignored -> usersApi.refreshUser(userId))
I am trying to run a thread on Android, with the following code:
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d("CustomImage", " touched : ${event!!.x}, ${event.y}")
Thread(Runnable {
if(threadRunning) {
return#Runnable
}
threadRunning = true
consumeTouchEvent(event)
}).start()
return super.onTouchEvent(event)
}
private fun consumeTouchEvent(event: MotionEvent) {
Log.d("CustomImage", " consumeTouchEvent x,y : ${event.x}, ${event.y}")
Thread.sleep(1000) //long running task
threadRunning = false
}
Here's the corresponding log after I touch the view:
01-03 00:17:05.654 8122-8122/me.exp D/CustomImage: touched : 960.0, 299.0
01-03 00:17:05.656 8122-8250/me.exp D/CustomImage: consumeTouchEvent x,y : 960.0, 509.0
As evident, the 'Y' value for touch event is 299.0, but in consume method, it gets updated to 509.
One possible explanation I could think was that multiple touch events are fired when I touch the screen and what's logged in consumeTouchEvent() is a later value - but in that case, onTouchEvent() should be logging all the touch values.
This problem goes away if I run consumeTouchEvent() without spawning a new thread.
Android framework may reuse instances of MotionEvent to avoid heap pollution and excessive garbage collection. The recycle method in particular is used to mark the event as immediately available for reuse:
Recycle the MotionEvent, to be re-used by a later caller. After calling this function you must not ever touch the event again.
Before your Runnable you've created has a chance to execute the onTouchEvent has already returned and thus Android has reused the MotionEvent instance.
You can obtain your own copy for processing like so:
val myEvent = MotionEvent.obtain(event)
// do some work
myEvent.recycle()
Your example would then look like:
val myEvent = MotionEvent.obtain(event)
thread {
if(threadRunning) {
return#Runnable
}
threadRunning = true
consumeTouchEvent(myEvent)
myEvent.recycle()
}
return super.onTouchEvent(event)
I am looking for a way, hopefully using RxJava for consistency, to monitor the progress of multiple subscribers that may be fired at different times. I am aware of how to merge or flatMap subscribers together when they are all fired from one method but I am unaware of a way to do it when they are fired at different times from different methods.
For example, if I have 2 long running tasks attached to button presses. I push button 1 and fire off the observable/subscriber, half way through running I push button 2 to fire off the second observable/subscriber.
I want to enable a button when no tasks are running and disable it when one or more tasks are running.
Is this possible? I am trying to avoid setting instance variable flags as well.
I would use a separate BehaviorSubject and scan to monitor execution status. This is quite similar to an instance variable, but probably it can inspire you to a better solution. Something like this:
private final BehaviorSubject<Integer> mProgressSubject = BehaviorSubject.create(0);
public Observable<String> firstLongRunningOperations() {
return Observable.just("First")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1)));
}
public Observable<String> secondLongRunningOperations() {
return Observable.just("Second")
.doOnSubscribe(() -> mProgressSubject.onNext(1))
.finallyDo(() -> mProgressSubject.onNext(-1));
}
public Observable<Boolean> isOperationInProgress() {
return mProgressSubject.asObservable()
.scan((sum, item) -> sum + item)
.map(sum -> sum > 0);
}
Usage will be like this:
isOperationInProgress()
.subscribe(inProgress -> {
if (inProgress) {
//disable controls
} else {
//enable controls
}
});
With this approach you can have any number of long running operation and you do not have to fire them all. Just don't forget to call doOnSubscribe and finallyDo.
PS. Sorry, I didn't test it, but it should work.
To make this possible, let both long running operations emit an onNext event on a PublishSubject. Combine both Subjects with a zip or combineLatest function and subscribe to this. Once the combine function receives an event, this means that both Subjects have emitted an onNext event, thus both long running operations have finished and you can enable the 3rd button.
private PublishSubject<Boolean> firstSubject = PublishSubject.create();
private PublishSubject<Boolean> secondSubject = PublishSubject.create();
#Override
public void onStart() {
super.onStart();
subscribeToResult();
}
private Observable<Integer> firstOperation() {
return Observable.just(100)
.delay(1000) // takes a while
.subscribe(tick -> firstSubject.onNext(true));
}
private Observable<Integer> firstOperation() {
return Observable.just(200)
.delay(1000) // takes a while
.subscribe(tick -> secondSubject.onNext(true));
}
private void subscribeToResult() {
Observable.zip(
firstSubject,
secondSubject,
(firstResult, secondResult) -> return true
).subscribe(
tick -> thirdButton.setEnabled(true)
)
}
Definitely take a look at the RxJava combine functions.