Sealed class is not new in other programming languages such as C# and Scala. The sealed class provides a way to organize our code to look a lot nicer and easy to work with.
/* Copyright 2020 Seanghay Yath.
SPDX-License-Identifier: Apache-2.0 */
@WorkerThread
suspend fun fetchPosts() {
val response = httpClient.posts()
withContext(Dispatchers.Main) {
when (response) {
is ResultOf.Success -> {
// destructing data object
val (posts) = response
// update the view
adapter.submitList(posts)
}
is ResultOf.Failure -> {
val (message, throwable) = response;
handlePostsFailure(messsage, throwable)
// log error message to logcat
Timber.e(throwable, message)
}
}
}
}
An example of how to use Sealed class for handling error and success
As you can see in the example above that there is no need to manually cast objects. Kotlin compiler knows what data type it should be.
Oftentimes, we use LiveData to store data from APIs or databases, however, data from those I/O operations could cause failure, so the best way to handle those errors and successful responses is to use Sealed classes.
/* Copyright 2020 Seanghay Yath (@seanghay)
SPDX-License-Identifier: Apache-2.0 */
sealed class ResultOf<out T> {
data class Success<out R>(val value: R): ResultOf<R>()
data class Failure(
val message: String?,
val throwable: Throwable?
): ResultOf<Nothing>()
}
In our ViewModels, it would look something similar to this. I assume that you all familiar with Kotlin Coroutines.
/* Copyright 2020 Seanghay Yath (@seanghay)
SPDX-License-Identifier: Apache-2.0 */
class PostViewModel: ViewModel() {
// Retrofit service
private val postService: PostService = HttpClient.get().postService()
// for internal usage
private val _posts = MutableLiveData<ResultOf<Post>>()
// Expose to the outside world
val posts: LiveData<ResultOf<Post>> = _posts
@UiThread
fun fetchPostsFromApi() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = postService.getAllPosts()
_posts.postValue(ResultOf.Success(response.data))
} catch (ioe: IOException) {
_posts.postValue(ResultOf.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_posts.postValue(ResultOf.Failure("[HTTP] error please retry", he))
}
}
}
}
After this, we can finally observe it from our Activities or Fragments.
/* Copyright 2020 Seanghay Yath (@seanghay)
SPDX-License-Identifier: Apache-2.0 */
class PostListFragment: Fragment() {
// `viewModels` is a extension from fragment-ktx
private val viewModel: PostViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// just a normal ListAdapter of RecyclerView
val adapter = PostListAdapter()
// set our adapter to the view
binding.recyclerView.adapter = adapter
// observe result from the ViewModel
viewModel.observe(viewLifecycleOwner, Observer { result ->
when (result) {
// there is no need for type-casting, the compiler already knows that
is ResultOf.Success -> {
adapter.submitList(result.value)
}
// here as well
is ResultOf.Failure -> {
showErrorMessage(result.message ?: "Unknown error message")
}
}
})
viewModel.fetchPostsFromApi()
}
}
Look through, these sample codes, we might be thinking it’s the best way to work with, but there is more we can do to improve it by using Kotlin extension functions.
#sealed-classes #kotlin #android #java