Just some implementation of the method from Chris Cook's answer:
At first, we need the object that will contain response data and exceptions:
/**
* A generic class that holds a value with its loading status.
*
* @see <a href="https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt">Sample apps for Android Architecture Components</a>
*/
data class Resource<out T>(val status: Status, val data: T?, val exception: Throwable?) {
enum class Status {
LOADING,
SUCCESS,
ERROR,
}
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(exception: Throwable): Resource<T> {
return Resource(Status.ERROR, null, exception)
}
fun <T> loading(): Resource<T> {
return Resource(Status.LOADING, null, null)
}
}
}
And then my own invention - AsyncExecutor.
This small class do 3 important things:
- Return standard convenient LiveData object.
- Call provided callback asynchronously.
- Takes the result of the callback or catch any exception and put it to the LiveData.
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
class AsyncExecutor {
companion object {
fun <T> run(callback: () -> T): LiveData<Resource<T>> {
val resourceData: MutableLiveData<Resource<T>> = MutableLiveData()
Thread(Runnable {
try {
resourceData.postValue(Resource.loading())
val callResult: T = callback()
resourceData.postValue(Resource.success(callResult))
} catch (e: Throwable) {
resourceData.postValue(Resource.error(e))
}
}).start()
return resourceData
}
}
}
Then you can create a LiveData in your ViewModel, contains the result of your callback or exception:
class GalleryViewModel : ViewModel() {
val myData: LiveData<Resource<MyData>>
init {
myData = AsyncExecutor.run {
// here you can do your synchronous operation and just throw any exceptions
return MyData()
}
}
}
And then you can get your data and any exceptions in the UI:
class GalleryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
galleryViewModel = ViewModelProviders.of(this).get(GalleryViewModel::class.java)
// ...
// Subscribe to the data:
galleryViewModel.myData.observe(viewLifecycleOwner, Observer {
when {
it.status === Resource.Status.LOADING -> {
println("Data is loading...")
}
it.status === Resource.Status.ERROR -> {
it.exception!!.printStackTrace()
}
it.status === Resource.Status.SUCCESS -> {
println("Data has been received: " + it.data!!.someField)
}
}
})
return root
}
}