Sealed Class To Update UI From Kotlin Flow
This post will give an example of using sealed classes in Kotlin to update the Android Compose UI from data captured in a Flow.

In this post I will show you an example of how I created a sample Sealed Class to update UI from Kotlin Flow.

The previous post here elaborates more on this concept with a full sample as well.

Flow to Update UI in Android Compose

As the last post, I am just going to post the code as I have in my sample. Methods and functions are together with least amount of class files. For better organization, it would be better to separate the classes.

I wanted to find a cleaner and better way to capture the state of an API request.

Curiosity and thirst for knowledge wanted to practice more with sealed classes.

My specific want was to implement a sealed class so I could use it in a when statement to update UI from Kotlin Flow.

Code

This is what is inside the ViewModel class

class ViewModel(val applicationContext : Context) : ViewModel() {

    private val resultRepo = ResultRepo(applicationContext)

    var resultFlow = MutableStateFlow<ResultFromAPI>(ResultFromAPI.isLoading("Loading...."))

    fun getToken(){
        viewModelScope.launch {
            resultRepo.getJWT().collect{
                resultFlow.value = it
            }
        }
    }

}

sealed class ResultFromAPI {
    data class success(val data : String) : ResultFromAPI()
    data class error(val exception : String) : ResultFromAPI()
    data class isLoading(val message : String) : ResultFromAPI()
}

data class Item(
    val item: String?
)

class ResultRepo(val context : Context){
    suspend fun getJWT() : Flow<ResultFromAPI> {
        return flow {
            emit(ResultFromAPI.isLoading("Loading response..."))
            delay(1000)
            val service = RetrofitHelper.getInstance(context).create(AccessApi::class.java)
            val response : Response<Item> = service.altertestJWT("Vegetables are healthy!")
            if(response.isSuccessful){
                val result = response.body()

                if(result == null){
                    emit(ResultFromAPI.error("Empty response..."))
                } else {
                    emit(ResultFromAPI.success(result.item.toString()))
                }
            } else {
                emit(ResultFromAPI.error("Empty response..."))
            }

        }.flowOn(Dispatchers.IO)
    }
}

class ViewModelFactory(val applicationContext : Context) : ViewModelProvider.Factory {

    //------------to create a ViewModel. This will ensure same ViewModel is used
    //val factory = ViewModelFactory()
    //val viewModel = ViewModelProvider(this, factory)[ViewModel::class.java]//by viewModels<ViewModel>()

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if(modelClass.isAssignableFrom(com.itgeek25.samplejwt.viewmodel.ViewModel::class.java)){
            com.itgeek25.samplejwt.viewmodel.ViewModel(applicationContext) as T
        } else {
            throw IllegalArgumentException("ViewModel Not Found!!")
        }
    }
}

Now this is what is inside the MainActivity

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val factory = ViewModelFactory(applicationContext)
        val viewModel = ViewModelProvider(this, factory)[ViewModel::class.java]

        enableEdgeToEdge()
        setContent {
            SampleJWTTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

                    var change by remember { mutableStateOf(0) }

                    var resultFromAPI by remember { mutableStateOf<ResultFromAPI>(ResultFromAPI.isLoading("")) }

                    LaunchedEffect(Unit) {
                        viewModel.resultFlow.collect{
                            resultFromAPI = it
                        }
                    }

                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(innerPadding)
                            .background(Color.Gray),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        when(resultFromAPI){
                            is ResultFromAPI.error -> {
                                Text(text = (resultFromAPI as ResultFromAPI.error).exception)
                            }
                            is ResultFromAPI.isLoading -> {
                                Text(text = (resultFromAPI as ResultFromAPI.isLoading).message)
                            }
                            is ResultFromAPI.success -> {
                                Text(text = (resultFromAPI as ResultFromAPI.success).data)
                            }
                        }
                        Button(onClick = {
                            viewModel.getToken()

                        }) {
                            Text(text = "Update")
                        }
                    }
                }
            }
        }
    }
}

Hope this was useful.

Leave a Reply

Your email address will not be published. Required fields are marked *