A well-known Use-case

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.

Sealed classes work well with LiveData and ViewModels

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

Kotlin sealed class for success and error handling
26.75 GEEK