Delay with respect of lifecycle
With the help of Kotlin delegates

Sometimes we need to use delay. To postpone some handling. To hide the keyboard, because otherwise, it will not work for some magical reason. To scroll to the new position of Recycler’s view list, when we start submitting a new one. To show keyboard right after a view is created in the fragment when we come back to it. To imitate a debounce feature and start work after some time of map being idle.
Usually, we use Handler.postDelayed for that:
recyclerView.postDelayed({ recyclerView.showKeyboard() }, 300L)
This looks fine for some small things, we want to adjust. But the devil in the detail. You see, we literally say to handler “please invoke this runnable after some time and I don’t care what will happen”. If we will give this runnable to the handler, then before it will invoke that, we may go off the screen and the page will be closed (eg Activity is destroyed, or Fragment is detached). Thus, we can have an inconsistent state, with which runnable’s internals will work. Moreover, we receive a leak of Fragment or Activity. This can lead to unpredictable bugs and crashes.
But wait. We have a presentation layer! Well, we can have some logic wrapped in the presentation layer, that is already under control. And if you splitting your app into layers, every such detail (especially scheduling some UI invocation) needs to happen in the presentation layer, because Presenter
(or anything else) encapsulates presentation logic in itself. But still we can have cases when we don’t want to put that into Presenter
. It can be some Android SDK strange behavior, or we may not have a presentation layer at all!
Then in that case generally we declare runnable as a separate variable and in onPause
/onStop
/onDestroyView
or else we call handler.removeCallbacks()
:
Sounds good, doesn’t it? Well, imagine that you need to write that all over again and again. Every time you need to remember that. People tend to forget things, so you can easily forget to remove those callbacks. And then you will start making a deal with yourself like “here is only 100ms delay, nothing will happen”. :) That is a critical spot here. Let’s eliminate that boilerplate!
Solution
If you read the article ‘Attach your presenters to view layer’ and eliminating boilerplate there, you probably imagine the approach we will use there. If not then do not hesitate to read the article, but it is not necessary since I will describe the idea further.
As with presenters in the aforementioned article we describe the container interface here:
interface PostDelayedContainer {
fun <T : View> T.delay(delayMs: Long, runnable: T.() -> Unit)
fun <T : View> T.cancelDelayed()
}
There will be his direct child as well, which will contain and encapsulate all necessary logic inside. Let’s call it PostDelayedLifecycleContainer
. All we need to do now is just plug it to our BaseFragment
. Everyone has it :)
Now in derived ExampleFragment
we can simply write recyclerView.delay(300L) { showKeyboard() }
and that it! Simple enough and we don’t need to think about handling lifecycle anymore.
Internals
Okay, we discussed the API of our approach and how we will plug it. Now it’s time to write an implementation of PostDelayedContainer
. Let’s start with the declaration of a special class PostDelayedPhase
. Its main duty to keep a map of currently active delays (that where scheduled by clients of that class):
As you can see, we actually not really bothering about removing Delayed
from the map after the desired runnable will executed. That may be not so good in terms of clean state, but actually the main purpose of the map to allow no more than 1 delayed each tick, not keeping a clean state of what is delayed and what is already not.
Homework: So if you want to clean it, let’s make a little bit of homework on that. And you can write in the comments enhanced version of that class with auto clean finished Delayed
instances.
Since we have a phase that handles delays somehow and we can start and stop that phase by calling start
and end
methods, we now able to create PostDelayedContainer
's implementation:
Tradeoff
The main tradeoff of our solution is handling only one delayed runnable at the time. If we want to pass new delayed runnable, the old one will be removed. In my current project, we completely fine with that since we use delay with simple things for views (scrolling content, showing keyboard, lifting App bar) and we want to have that interchange possibility to now handle that by ourselves.
But if you want, you can change that contract and implementation to support multiple delayed actions:
A couple of notes here:
PostDelayedContainer.delay
now returns an instance ofDelayed
Delayed
is an interface and you can control each delay by force stop or start it whenever you need- We added auto removing
Delayed
fromviewMap
since we not controlling it anymore. With this approach, we keep it clean. - We use special
removeQueue
and checking whether we right now iterate through the whole map or part of it (isTraversing
property) because otherwise, we can accidentally remove elements in the middle of an iteration. And this will causeConcurrentModificationException
. - Our solution is not thread-safe. We assume that this code will be run only in Main Thread.
Now you have a possibility to control each delay separately. But at the same time, you can not have interchangeable delays (like our first implementation). What is more preferable to you, you decide. For us, the first approach is more appropriate. Like I said we don’t use delays too much, so for us, the interchangeability is much more important.
P.S.: You can also try to combine both approaches (to have interchangeability of some delays and at the same time multiple delays). Let it be your other homework. Don’t forget to share your solutions ;)

If you liked that article, don’t forget to support me by clapping and if you have any questions, comment me and let’s have a discussion. Happy coding!
Also, there are other articles, that can be interesting: