The RecyclerView
, unlike to ListView
, doesn\'t have a simple way to set an empty view to it, so one has to manage it manually, making empty view vi
In scenarios like these where the API is designed so poorly for something as trivial as this I just smartly brute-force it.
You can always just run a background task or Thread that periodically polls if the animator is running and when it's not running, execute the code.
If you're a fan of RxJava, you can use this extension function I made:
/**
* Executes the code provided by [onNext] once as soon as the provided [predicate] is true.
* All this is done on a background thread and notified on the main thread just like
* [androidObservable].
*/
inline fun T.doInBackgroundOnceWhen(
crossinline predicate: (T) -> Boolean,
period: Number = 100,
timeUnit: java.util.concurrent.TimeUnit =
java.util.concurrent.TimeUnit.MILLISECONDS,
crossinline onNext: T.() -> Unit): Disposable {
var done = false
return Observable.interval(period.toLong(), timeUnit, Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.takeWhile { !done }
.subscribe {
if (predicate(this)) {
onNext(this)
done = true
}
}
}
In your case you can just do:
recyclerView.doInBackgroundOnceWhen(
predicate = { adapter.isEmpty && !recyclerView.itemAnimator.isRunning },
period = 17, timeUnit = TimeUnit.MILLISECONDS) {
updateEmptyView()
}
What this does is it checks if the predicate is satisfied every 17 milliseconds, and if so will execute the onNext block. (17 millis for 60fps)
This is computationally expensive and inefficient... but it gets the job done.
My current preferred way of doing these things is by making use of Android's native Choreographer
which allows you to execute callbacks on the next frame, whenever that may be.
Using Android Choreographer:
/**
* Uses [Choreographer] to evaluate the [predicate] every frame, if true will execute [onNextFrame]
* once and discard the callback.
*
* This runs on the main thread!
*/
inline fun doOnceChoreographed(crossinline predicate: (frameTimeNanos: Long) -> Boolean,
crossinline onNextFrame: (frameTimeNanos: Long) -> Unit) {
var callback: (Long) -> Unit = {}
callback = {
if (predicate(it)) {
onNextFrame(it)
Choreographer.getInstance().removeFrameCallback(callback)
callback = {}
} else Choreographer.getInstance().postFrameCallback(callback)
}
Choreographer.getInstance().postFrameCallback(callback)
}
A word of warning, this is executed on the main thread unlike with the RxJava implementation.
You can then easily call it like so:
doOnceChoreographed(predicate = { adapter.isEmpty && !recyclerView.itemAnimator.isRunning }) {
updateEmptyView()
}