Proper way of using sealed interface in kotlin - android

I am totally new in sealed Interface in kotlin. I am trying to state management through sealed in android kotlin. My main goal is when I used the object of sealed i don't want to inherit all child. I am not sure correctly is that sealed interface is right choice for me. All my code may be wrong, please correct if I am wrong Thanks.
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : BluetoothConnectionUIState()
data class ScanningDevice(val storedDevice: SnapshotStateList<BluetoothDevice>? = null) : ConnectionUIState
}
I initialise the variable like this
var uiState by mutableStateOf<BluetoothConnectionUIState>(BluetoothConnectionUIState.Initial)
private set
Now I am passing the uiState variable in the function and using when statement
when (uiState) {
BluetoothConnectionUIState.ScanningDevice -> {
xuz()
}
}
why when statement is giving error
'when' expression must be exhaustive, add necessary 'Initial' branch or 'else' branch instead
Also this line is also giving me error BluetoothConnectionUIState.ScanningDevice in when statement.
Error
Classifier 'ScanningDevice' does not have a companion object, and thus must be initialized here
If I am doing wrong here. Can you please elaborate of 2 point of this stack overflow. Thanks
UPDATE
I did some changes
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : ConnectionUIState
data class ScanningDevice(val storedDevice: SnapshotStateList<BluetoothDevice>? = null) : BluetoothConnectionUIState()
}
I did success on when statement that it's not complaining about Initial
when (uiState) {
is BluetoothConnectionUIState.ScanningDevice -> {
BluetoothPairContent(viewModel, tryAgainAction, openSettingAction, scanDeviceList)
}
}
That is my goal, but another problem raised that it gives error in uiState initialise time
var uiState by mutableStateOf<BluetoothConnectionUIState>(BluetoothConnectionUIState.Initial)
private set
Error
Type mismatch.
Required:
BluetoothConnectionUIState
Found:
BluetoothConnectionUIState.Initial
Again I am confused. Please guide me on this. Thanks

(I think you worked it out but just in case - you need is in your when to check if something is a class. When comparing to an object you use equality, which can just be written as the value to match)
Working off your update:
// simplified for readability
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : ConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
}
You've got this object and class nested inside BluetoothConnectionUIState, which means their fully qualified names are things like BluetoothConnectionUIState.Initial. But you don't actually have to nest them, you can do this:
sealed class BluetoothConnectionUIState
object Initial : ConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
Now Initial and ScanningDevice aren't nested inside BluetoothConnectionUIState, you just reference them directly. So what's the relationship between them now? Look at the constructors:
// subclass of BluetoothConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
// implements the ConnectionUIState interface
object Initial : ConnectionUIState
Once you remove the nesting, you can see that Initial actually has no type relationship with the sealed class at all! It just happened to be located inside it. And that's why you can't put it in your mutableStateOf - it's not a BluetoothConnectionUIState.
It's also why you were getting the must be exhaustive error in your original when block - you only had a branch checking Initial, which isn't part of the sealed class anyway. It works when you check ScanningDevice, because that's the only member of the class - if uiState is a BluetoothConnectionUIState, it must be a ScanningDevice.
How you fix this is up to you - seems like you want those two things to be part of the same sealed class. Maybe you want ConnectionUIState to be the sealed class? Since that's what they both represent. And have BluetoothConnectionUIState be the interface that you can apply selectively to certain members of that sealed class?
sealed interface BluetoothConnectionUIState
sealed class ConnectionUIState {
object Initial : ConnectionUIState()
data class ScanningDevice : ConnectionUIState(), BluetoothConnectionUIState
}
This overview might be a helpful read too!

Related

How can a method from an interface can be called in the given project? (Whether I'm wrong?)

This is the project I'm trying to understand. I'm trying to understand what goes after what in this project. What confuses me, is that getFeatures() call in ViewModel. It seems that it calls an abstract function in the interface which is implemented in the file DefaultMapsRepository. I don't understand how it works. I thought that call should be of function from DefaultMapsRepository class where getFeatures() is implemented. So, as I understand, getFeatures() call in ViewModel calls the not implemented method from the interface and then that interface somehow finds the implementation of it and that override fun getFeatures() code runs its body. Correct me if I'm wrong. But If I create another implementation of getFeatures() how would that interface choose which implementation to use? I heard somewhere that interface of repository makes code easier to test but it's hard to understand how if I don't know how this all code works. I sometimes like to test how code works in the console to make code look simpler but I can't replicate the same situation because I'm not able to run the code if I add something to fun main() constructor. I think such structure of a project is used a lot and I want to understand it very well.
A different way of explaining it that might help. Suppose you have this interface and class.
interface Pet {
fun sayName()
}
class Dog(val name: String): Pet {
override fun sayName() {
println(name)
}
}
If some function asks for a Pet, you can pass it an instance of anything that implements the Pet interface.
fun sayPetName(pet: Pet) {
pet.sayName()
}
From the function's point of view, it doesn't have to know what class was passed to it. It just knows that whatever was passed to it is an instance of a Pet, and must therefore have a non-abstract sayName() function.
Even though the function sayName() is abstract inside the definition of the interface, it would be impossible to create an instance of a class where there is an abstract function. There's no such thing as an abstract function in an instance of a class. You can define abstract classes that have abstract functions, but you cannot create instances of them.
You could pass a Dog instance to this function since it qualifies as a Pet. When the function calls pet.sayName(), it is up to the instance of Dog to respond. The function itself doesn't have to know anything about what class type was passed to it.
In the same way, if you have an instance of a Pet that was passed to your constructor, your class can use the sayName() function on it. If you pass the class a Dog instance, even though the class is storing it in a property that is marked Pet and the class doesn't know it has a Dog, if it calls sayName() on it, the Dog instance will use its implementation of sayName().
class SomeClass(val pet: Pet) {
fun sayThePetName() {
pet.sayName()
}
}
If a Dog were passed to the constructor of the above class, it does not get "downgraded" into a Pet interface that has an abstract function. It's still a Dog instance even if that specific type is not exposed to the class in this scope. The code inside the class doesn't know or care that it's a Dog. It just knows it has a reference to some actual class instance that has the functions defined by the Pet interface.
An interface is just a contract. If CoolInterface has a property someValue: Int and a method doThing(): Boolean, then anything that implements that interface is guaranteed to have that property and that method.
The MapsViewModel class takes a MapsRepository parameter, and MapsRepository is just an interface with one function:
suspend fun getFeatures(): Resource<FeaturesResponse>
You can pass in anything that implements that interface, which DefaultMapsRepository does:
class DefaultMapsRepository #Inject constructor(
...
) : MapsRepository {
Since it implements that interface, it needs to implement that getFeatures function, which it does:
// override is the keyword that shows you're not just declaring a new function,
// it's implementing something abstract in a superclass/interface (or overriding
// an open function)
override suspend fun getFeatures(): Resource<FeaturesResponse> {
// bla bla
}
So you can pass in anything at all as your repository, so long as it implements MapsRepository, meaning you need some implementation of that getFeatures function. It doesn't "decide" a version to use, it calls it on whatever you pass in. So you can test it with whatever mock class or object you like
val mockRepo = object : MapsRepository {
override suspend fun getFeatures(): Resource<FeaturesResponse> {
return Resource.Error("oh snap")
}
}
val viewModel = MapsViewModel(mockRepo, whateverTheOtherThingIs)
// calls getFeatures() on the repo
viewModel.getFeatures()
If none of that makes any sense, you should read up on what interfaces and polymorphism are!

Kotlin: How to instantiate a class and implement an interface in a single line of code?

I'm trying to do something similar to :
val telephonyCallback = object: TelephonyCallback() implements TelephonyCallback.ServiceStateListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
}
}
Is it possible with Kotlin?
You can provide multiple supertypes separated by commas:
val telephonyCallback = object: TelephonyCallback(), TelephonyCallback.ServiceStateListener {}
Just note it does not work as you may expect. Object expressions create anonymous classes and that means there is no explicit class that extends/implements both supertypes. In some cases you can use telephonyCallback as both supertypes (for example if it's a local variable), in other cases you can only keep one of its supertypes and you have to choose it explicitly (property, function parameter).
If you want to be able to pass such telephonyCallback variable anywhere, you have to create a regular class for it, so it can be referenced in the code.

Should object definitions in sealed classes start with capital or lowercase letter?

For example :
sealed class Event {
object ViewClicked : Event()
}
Should it be ViewClicked or viewclicked?
It seems its a variable , so i guess it should be viewclicked but the rest of the codebase on the app has capitalized objects. So i wanted to know whats the right approach here?
Also , can anyone confirm if object is like a static variable in Java?
ViewClicked is not a variable. Declaring object ViewClicked we show that we have only one instance (singleton) of class ViewClicked, so it is definition of a class and creation of its instance on one line.
By Kotlin style guide:
Names of classes and objects start with an upper case letter and use the camel case.
Therefore the correct definition would be:
sealed class Event {
object ViewClicked : Event()
}

#Parcelize and Superclass, doubling data because of constructor requirement

I have a Result parcelable class that is supposed to serve as a container for a key and a parcelable data. This is how I define it:
#Parcelize
open class Result<out T : Parcelable>(val key: String, val data: T?) : Parcelable
The problem is that when defining the child class, in order for #Parcelize to work, I need to add val to the object in the constructor, essentially making the data being written twice to the parcel twice. Here is an example:
#Parcelize
class LessonFinishedResult(private val lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
I would like to have this:
#Parcelize
class LessonFinishedResult(lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
But it is not allowed. Is there a smart way I can get around this? And another thing, is there a way I can avoid having to manually use Result<LessonActivityData> and have the type be inferred automatically from the object being passed?
Thanks!
essentially making the data being written twice to the parcel twice
It isn't; the code generated by #Parcelize doesn't care about the superclass at all. It can even not be Parcelizable. Only the primary constructor parameters (in this case val lessonActivityData) are written/read, and when reading it's enough to pass the correct parameters to the superclass constructor.

Initializing Retrofit client in application

Like below?
val retro: Retro by lazy {
PilotApp.retro!!
}
class PilotApp : Application() {
companion object {
var retro: Retro? = null
}
override fun onCreate() {
retro = Retro(applicationContext)
super.onCreate()
}
}
Is this a good way of initialisation? Thanks in advance.
This is not following the rule of "Inversion of Control" and It is not a good idea.
The reason it is not a good idea is because whenever a class (like a ViewModel, Fragment or Activity or ...) wants to use the retro, they have to get the Retro object themselves by calling your first line (PilotApp.retro).
The alternative (called Dependency Injection/Inversion of Dependency) is that the Retro object is given to the class (again, the ViewModel or whatever) when it is initialized.
The reason why this is important is because with the second approach, you can make your classes that use the Retro, testable. You can give them RetroMock or TestRetro that does what you want (for example, mock an api to return an error).
Another note for your example, you don't need to make the retro nullable, you should make your var a lateinit and make it non-null.

Categories

Resources