Kotlin Basics

According to the Kotlin documentation:

Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear outside the module and package within which the sealed class is defined.

In other words, Sealed classes are used to define a restricted hierarchy of classes and each of these subclass will be representing some kind of state.

Let’s say you want to define a class “Time” with value either “Day” or “Night”. You can do this with Enum class and sealed class both as follows:-

enum class TimeEnum {
Day,
Time
}
sealed class Time {

object Day:Time()
object Night:Time()
}

Now let’s say, We want some data (let it be “timeUnit”) in the states “Day” and “Night”, again we can do it using enum class and sealed class both as follows:-

enum class TimeEnum (val timeUnit:String){
Day(timeUnit = "AM"),
Time(timeUnit = "PM")
}
sealed class Time {
data class Day(val timeUnit: String) : Time()
data class Night(val timeUnit: String) : Time()
}

Then what’s the difference between Sealed class and enum class ?
Very first difference you can see, we have passed the value of timeUnit in enum class constants but have not passed any value for timeUnit in subclasses of sealed class.
Enum class need values of parameters on compile time while we can pass the values of parameters in sealed class on runtime.

Lets dig more.

Let’s say I want to pass different numbers of data in every state.

Using enum :

enum class TimeEnum (val timeUnit:String,val someRandomData:String){
Day(timeUnit = "AM","Good Morning"),
Time(timeUnit = "PM") // this will give exception
// No value passed for parameter 'someRandomData'
}

To handle heterogenous type of data, we need sealed class :-

sealed class Time {
data class Day(val timeUnit: String,val someRandomData:String) : Time()
data class Night(val timeUnit: String) : Time()
}

This code will run successfully.

Let’s see the decompiled code of above sealed class:

public abstract class Time {
private Time() {
}

public Time(DefaultConstructorMarker $constructor_marker) {
this();
}

public static final class Day extends Time {
@NotNull
private final String timeUnit;
@NotNull
private final String someRandomData;

@NotNull
public final String getTimeUnit() {
return this.timeUnit;
}

@NotNull
public final String getSomeRandomData() {
return this.someRandomData;
}

public Day(@NotNull String timeUnit, @NotNull String someRandomData) {
Intrinsics.checkNotNullParameter(timeUnit, "timeUnit");
Intrinsics.checkNotNullParameter(someRandomData, "someRandomData");
super((DefaultConstructorMarker)null);
this.timeUnit = timeUnit;
this.someRandomData = someRandomData;
}

}
public static final class Night extends Time {
@NotNull
private final String timeUnit;

@NotNull
public final String getTimeUnit() {
return this.timeUnit;
}

public Night(@NotNull String timeUnit) {
Intrinsics.checkNotNullParameter(timeUnit, "timeUnit");
super((DefaultConstructorMarker)null);
this.timeUnit = timeUnit;
}

}
}

If you will see the decompiled code, You will find :-

  1. A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.
  2. Constructors of sealed classes is private (but can have one of two visibilities: protected (by default) or private)

Now discuss perfect use case of Sealed class. It is API RESPONSE HANDLING

As we know, API calling response can have only three states i.e. Success, Failure and Loading.

Let’s create sealed class for this ->

sealed class ApiResponse<T>{

class Success<T>(data: T) : ApiResponse<T>()

class Error<T>(message: String?, data: T? = null) : ApiResponse<T>()

class Loading<T> : ApiResponse<T>()

}

Let’s consider our apiResult is being stored in variable response of viewModel.

var response: MutableLiveData<ApiResponse<Any>> = MutableLiveData()

Now we need to observe the response and render the UI according to state of response.

viewModel.response.observe(viewLifecycleOwner) { response ->
when (response) {
is ApiResponse.Success -> {
response.data?.let {
//bind the data to the ui
}
}
is ApiResponse.Error -> {
//show error message
}

is ApiResponse.Loading -> {
//show loader, shimmer effect etc
}
}
}

This is how we can handle API response using sealed class.

  1. Sealed classes are similar to enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances, each with its own state.
  2. Sealed class can store heterogeneous type of data in their subclasses.
  3. A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.
  4. Constructors of sealed classes is private (but can have one of two visibilities: protected (by default) or private)

Source link