Table Of Contents:
Disposable
RxJava 2 introduced the concept of Disposables .
Disposables are streams/links/connections between Observer
and Observable
. They’re meant to give power to the Observers
to control Observables
. In most cases, the Observer
is a UI element, ie; Fragment
, Activity
, or a corresponding viewmodel of those elements. While Observable
represents a data flow where data can be passed from one source to another and get modified along the way if needed.
Sometimes that data flow can be long-running api requests, or can be listening to the updates in the database, and by the time you get a response or a value gets emitted - the screen that made a request might already be closed. In this case, you might end up with a memory leak, or even a crash - depending on your implementation.
So in order to avoid these issues, it is important that ui elements which are aware of Android Lifecycles, dispose their data flows when they’re not needed anymore, even if no response was emitted yet. That’s where Disposable
comes handy.
Disposable
itself is an interface that comes with dispose()
and isDisposed()
functions. In RxJava 1 there was a Subscription
interface that had the same functionality. RxJava 3 introduced additional operations
.
This is how to declare and assign a disposable within a Fragment
:
1 private lateinit var disposable: Disposable
2
3 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
4 super.onViewCreated(view, savedInstanceState)
5
6 disposable = api.getPosts()
7 .observeOn(AndroidSchedulers.mainThread())
8 .subscribe({ posts ->
9 println(posts)
10 }, { throwable ->
11 println(throwable)
12 })
13 }
And below is the sample code to dispose the observable when the fragment’s view is destroyed; therefore, there is no subscriber listening to the changes:
1 override fun onDestroyView() {
2 if(this::disposable.isInitialized && !disposable.isDisposed) {
3 disposable.dispose()
4 }
5 super.onDestroyView()
6 }
CompositeDisposable
As an app evolves and grows, it may require getting data from multiple sources. For example, Instagram Feed screen has the endpoint to get the list of the posts and another endpoint to get the list of stories(you can notice how these two parts get updated asynchronously and don’t depend on each other 😉). The list of additional data flows can be big or small.
Instead of declaring and initializing each disposable for those data flows, RxJava 2 introduced a CompositeDisposable
class to bundle multiple Disposables
that can be disposed all at once. It’s basically the class that holds a collection of disposables
and also offers O(1) time complexity for add(Disposable)
, remove(Disposable)
, delete(Disposable)
operations.
Let’s continue with the example above, and add getStories()
call:
1 private val compositeDisposable by lazy {
2 CompositeDisposable()
3 }
4
5 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6 super.onViewCreated(view, savedInstanceState)
7
8 compositeDisposable.add(
9 api.getPosts()
10 .observeOn(AndroidSchedulers.mainThread())
11 .subscribe({ posts ->
12 println(posts)
13 }, { throwable ->
14 println(throwable)
15 })
16 )
17
18 compositeDisposable.add(
19 api.getStories()
20 .observeOn(AndroidSchedulers.mainThread())
21 .subscribe({ stories ->
22 println(stories)
23 }, { throwable ->
24 println(throwable)
25 })
26 )
27 }
Upon onDestroyView()
the CompositeDisposable
gets cleared:
1 override fun onDestroyView() {
2 compositeDisposable.clear()
3 super.onDestroyView()
4 }
Unlike Disposable
, CompositeDisposable
has two ways of disposing subscriptions:
dispose()
clear()
Both operations dispose the previously contained subscriptions, but dispose()
operation assigns true
to the disposed
variable, which later does not let you add, remove,or delete any other disposables.
If you have CompositeDisposable
in fragments, then you should call clear()
upon onDestroyView()
because the fragment doesn’t get destroyed. It goes to the backstack, therefore you don’t need to create a new instance of CompositeDisposable
every time when a fragment’s view is re-created.
If you have CompositeDisposable
in activities, then call dispose()
upon onDestroy()
.
SerialDisposable
While CompositeDisposable
is quite popular due to its convenience, there is the lesser known SerialDisposable
class, that RxJava 2 introduced. SerialDisposable
is a disposable container that allows atomically updating/replacing the contained Disposable with another Disposable, disposing the old one when updating, and handling the disposition when the container itself is disposed.
Or simply, whenever you want to replace the existing subscription with the new one - use SerialDisposable
.
For example, Instagram has a search functionality where their api returns results for each search query. A user first typed in fru
in EditText
, saw an empty screen, and then finished typing the whole word fruits
. Since there could be cases of unstable internet connections which can’t guarantee the order of network request/responses, in our use case, the first response came back for fruits
, then the second response was for fru
. Because the second response was the last response - ui renders the results of fru
, which is confusing for the user since they now see more broader results than the specific fruits
results.
In order to prevent these confusions, it’s better to use set()
operation of SerialDisposable
, which sets the latest disposable whenever the request happened and disposes the previous one:
1 private val serialDisposable by lazy {
2 SerialDisposable()
3 }
4
5 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6 super.onViewCreated(view, savedInstanceState)
7
8 view.editText.doAfterTextChanged { text ->
9 serialDisposable.set(
10 api.searchFor(text)
11 .observeOn(AndroidSchedulers.mainThread())
12 .subscribe({ result ->
13 println(result)
14 }, { throwable ->
15 println(throwable)
16 })
17 )
18 }
19 }
Operation replace()
on the other hand, does not dispose the previous disposable, which means the result of it will be emitted upon subscribe()
.
Think of SeriaDisposable
as a manual version of switchMap()
operator, that you can use to glue your ui callbacks and reactive data flows.
Conclusion
In conclusion, ui elements can subscribe to observables and decide when to listen to its changes and when not to. Whenever the ui element gets removed from the content view, it’s important to dispose all of its subscriptions, otherwise they will cause a memory leak.
RxJava provides Disposable
to manage the subscriptions. It also has CompositeDisposable
and SerialDisposable
classes that implement Disposable
.
CompositeDisposable
is a container that can hold multiple disposables and dispose them all at once.
SerialDisposable
is a container that is used to replace the current disposable with the new one.
I hope this post was helpful. If you have questions or something is unclear - please leave a comment!