Android SwipeRefreshLayout Spinner hides before completion - android

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)

Related

How auto-scrolling lyrics feature is implemented in Android? (UI point of view)

Its been a while I'm developing android applications but I haven't come across such a "View" in Android that facilitates the auto-scrolling. What is the Android "View Component" that might have been possibly used in e.g. Spotify's auto-scrolling mechanism
First I thought it would be a video synced with song's time but since, we can move it up and down, that might not be the case. How auto-scrolling is implemented in such way?
You can send some data from your sever to client app which contanis the pace of the singer inside that song because this time is different per every song as you know.
However, for implementing these kind of functionalities, you can call a function over and over which is responsible to scroll the content on every call. And to prevent conflict between the scroll method calls, you can check some flags and listen to scroll state change events of the scrolling content view which could be ScrollView, RecyclerView and etc. I will write some example to make it more clear for you.
If you want to use handler for example:
val handler = Handler(Looper.getMainLoooer())
val runnable = Runnable {
scrollContent()
handler.postDelayed(this,50)
}
var isScrolling = false
someScrollView.addOnScrollListener(object: OnScrollStateChangeListener {
override fun onScrollStateChanged(scrollView: ScrollView,newState: Int) {
if(newState == ScrollView.SCROLL_STATE_IDLE) {
isScrolling = false
} else {
isScrolling = true
}
}
})
// For example I defined the amount of scrolling 100 but you should calculate it based on the pace of singer inside the song actually
val scrollAmount = 100
private fun scrollContent(){
if(isScorlling == true) return
someScrollView.smoothScroll(scrollAmount,0)
}
handler.post(runnable)

Android LiveData: How to avoid data transfer from the database when an app is re-opened from the background?

My Android app is listening on a Firebase Database. Whenever my activity becomes inactive, I stop the listener, and when the activity becomes active again, I restart listening. This is done using LiveData and the 'onActive' and 'onInactive' methods as below:
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
Using the debugger, I have noticed that when I press the back button and close the app, the onInactive method gets called, and the app goes in the background. When I reopen the app, by picking it among the apps that are in the background, the onActive method gets called. However, in this case, all my data is reread from the database which will consume data bandwidth.
My question is:
What is the best way to avoid the re-reading of the data every time the app is coming back from the background?
Thanks
What you will need to do is set a "timeout" of sorts on your LiveData so that it defers becoming inactive for as much delay as you deem appropriate.
I implemented a "LingeringLiveData" superclass for exactly this situation. You can see it in a project of mine on GitHub. It's written in Kotlin, but you should be able to port it to Java without much trouble.
Subclasses need to provide implementations for startLingering and stopLingering that mirror what you would normally do in onActive and onInactive.
Basically, it sets up a timer to delay a call to endLingering after onInactive has been invoked, but only if onActive isn't invoked before that time expires. This lets you app stop and start without losing the listener.
abstract class LingeringLiveData<T> : LiveData<T>() {
companion object {
private const val STOP_LISTENING_DELAY = 2000L
}
// To be fully unit-testable, this code should use an abstraction for
// future work scheduling rather than Handler itself.
private val handler = Handler()
private var stopLingeringPending = false
private val stopLingeringRunnable = StopLingeringRunnable()
/**
* Called during onActive, but only if it was not previously in a
* "lingering" state.
*/
abstract fun beginLingering()
/**
* Called two seconds after onInactive, but only if onActive is not
* called during that time.
*/
abstract fun endLingering()
#CallSuper
override fun onActive() {
if (stopLingeringPending) {
handler.removeCallbacks(stopLingeringRunnable)
}
else {
beginLingering()
}
stopLingeringPending = false
}
#CallSuper
override fun onInactive() {
handler.postDelayed(stopLingeringRunnable, STOP_LISTENING_DELAY)
stopLingeringPending = true
}
private inner class StopLingeringRunnable : Runnable {
override fun run() {
if (stopLingeringPending) {
stopLingeringPending = false
endLingering()
}
}
}
}
Jetpack LiveData KTX now also offers a liveData convenience constructor that accepts a similar timeout parameter and runs code in a coroutine. You won't be able to use at all from Java, but it's nice to know about.
The easiest way to decrease data downloads in this case is to enable disk persistence in the Firebase client.
With disk persistence enabled, the Firebase client will write any data it gets from the server to a local disk cache, cleaning up older data if the cache gets too big.
When the client is restarted, the client will read the data from disk first, and then only request updates from the server using a so-called delta sync. While this delta sync still transfers data, it should typically be significantly less than the total data at the location you listen on.

How to efficiently show loading dialog if kotlin coroutine job takes quite some time to complete?

What I want to do is to use kotlin coroutines for database operations and show users a loading screen in the meantime. My basic implementation is as follows:
fun loadSomeData(){
mainCoroutineScope.launch {
showLoadingDialog()
// suspening function over Dispatchers.IO, returns list
val dataList = fetchDataFromDatabase()
inflateDataIntoViews(dataList)
hideLoadingDialog()
}
}
This works perfectly for me when the loading takes quite some time for large datasets. But in scenarios where the fetchDataFromDatabase() finishes quickly, showing and hiding the dialog box in quick succession creates an annoying glitching effect.
So what I want is to show the dialog box only if the fetchDataFromDatabase() function take more than, lets say, 100 ms to complete.
So my question is, what is the performance efficient way to achieve this using kotlin coroutines?
Here's an idea:
fun loadSomeData(){
mainCoroutineScope.launch {
val dialogJob = launch {
delay(1000)
try {
showLoadingDialog()
coroutineContext.job.join()
} finally {
hideLoadingDialog()
}
}
val dataList = fetchDataFromDatabase()
inflateDataIntoViews(dataList)
dialogJob.cancel()
}
}
When you cancel the dialogJob, it should either hit the delay statement and prevent showing the dialog, or the join statement, which will cause the finally block to execute and hide it.
Here is how I have achieved this, without using !! not null operator:
val deferred = lifecycleScope.async(Dispatchers.IO) {
// perform possibly long running async task here
}
lifecycleScope.launch (Dispatchers.Main){
// delay showing the progress dialog for whatever time you want
delay(100)
// check if the task is still active
if (deferred.isActive) {
// show loading dialog to user if the task is taking time
val progressDialogBuilder = createProgressDialog()
try {
progressDialogBuilder.show()
// suspend the coroutine till deferred finishes its task
// on completion, deferred result will be posted to the
// function and try block will be exited.
val result = deferred.await()
onDeferredResult(result)
} finally {
// when deferred finishes and exits try block finally
// will be invoked and we can cancel the progress dialog
progressDialogBuilder.cancel()
}
} else {
// if deferred completed already withing the wait time, skip
// showing the progress dialog and post the deferred result
val result = deferred.await()
onDeferredResult(result)
}
}
The main purpose of loading dialog is to prevent user from touching the UI while loading, but there is no guarantee about that. There is always a chance user touches some button before the dialog pops up.
A better way to do this is just disable or hide the UI components, or in general, show a "loading version" of your UI.
It is better to let user cancel the loading instead of setting a short timeout, so you may still need a dialog or snackbar showing a cancel button, or you can make a task manager page in your app, that would be really complicated though.

How to delay events for next observers?

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

RxJava simultaneous remove operations

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

Categories

Resources