Search code examples
androidkotlinandroid-jetpack-composekotlin-sealed

Proper way of using sealed interface in kotlin


I am totally new to sealed Interface in Kotlin. I am trying to state management through sealed in Android Kotlin. My main goal is when I use the object of sealed I don't want to inherit all children. I am not sure if sealed interface is the right choice for me. All my code may be wrong, please correct me 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


Solution

  • (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!