Recently I’ve been working on an Android app for Animal Crossing. Who doesn’t love Animal Crossing, right? So do I and what I tend to do is write software around things that I like.
The app downloads data from my API and displays this to the user in a clean and organized way. While doing this it came to my attention that I’d sometimes see refreshes in my lists even though the data is the same.
When using the app this feels kind of weird. You are already looking at a list filled with content, and then all of the sudden it refreshes. So I set out to change that.
A section of the code handling the downloaded data looks roughly like this:
private fun onScraped(result: String) { Single.fromCallable { val parsedData = parseData(result) return@fromCallable parsedData }.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ storeData(it) }, { onParsingFailed(it) }) .addTo(disposables) }
What this does is quite straight forward. The onScraped function gets called with the JSON data that has been retrieved from my API. This is parsed, and returned for any subscribers to work with.
The only thing the subscriber does is store the data in the local storage for the app, so that the views dependent on that data can use it, and react to new data using a Subject.
Okay, so that’s nice, and it works. But now when the data is the same, I don’t need to store it again in my local storage. And I don’t need the subscribers to update their views if the data is the same as before.
That should be an easy fix, we could do something like this:
private fun onScraped(result: String) { Single.fromCallable { val parsedData = parseData(result) return@fromCallable if (!isTheSame(parsedData)) { parsedData } else { null } }.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ storeData(it) }, { onParsingFailed(it) }) .addTo(disposables) }
What we did now is simply just checking if the data is the same as we already have, and then return null. Otherwise we just return the new data. (In this example we’re assuming that the storeData function handles checking for null and then ignores it.)
However, this doesn’t work. Single doesn’t allow you to return a null value, and it will die if you try to do so.
Luckily, to be able to return null we can use Maybe. The structure of the subscription changes slightly, but that’s no problem. Here’s what it looks like when changed:
private fun onScraped(result: String) { Maybe.fromCallable { val parsedData = parseData(result) return@fromCallable if (!isTheSame(parsedData)) { parsedData } else { Maybe.empty<OurDataType>() } }.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ storeData(it) }, { onParsingFailed(it) }, { // Left empty on purpose }) .addTo(disposables) }
We changed Single.fromCallable to Maybe.fromCallable, instead of null we return empty through Maybe, and added an extra callback to the Subscribe function that we leave empty. That one is to catch the OnComplete callback.
Except that there’s one little problem with this. My mistake was assuming that I needed to return an empty Maybe, but this is not the case. What you actually need to return is simply null. The reason for this is because my data type is actually a list.
If you’re using a list as your data type, then using Maybe.empty<List<foo>>() will just return an empty list and will trigger the success callback with that empty list. If you want the complete callback to be called, you should just return null.