Generating UI state in android for jetpack compose using sealedX library
Published: 2023-05-07 16:20:51

Hello guys, welcome back .In the previous article,
we learnt about [stateful and stateless composables](https://medium.com/@peterkagure/stateful-and-stateless-components-in-jetpack-compose-18f89f790750) 
and how to use them. In this article, we are going to learn how to generate UI states to avoid repetition

In android, we normally wrap our UI state in data classes to have a single point of reference when is comes to
modifying the state. A common type of state we use when building our app is as shown below

```

data class HomeScreenState(
        val msg: String = "",
        val posts: List<Post> = emptyList(),
        val success: Boolean = false,
        val errorMessage: String = "",
        val isLoading: Boolean = false,


        )
private val _state = mutableStateOf(HomeScreenState())
val state: State<HomeScreenState> = _state
```
The data class above wraps all our UI state in one class.The state here is for a screen that 
fetches data from a remote API and displays the result on the screen. If your app is mostly presentational and
depends on data from remote APIs, Then most of your screen states look like this.
These types of data classes become repetitive, and we don't like repetitive code so what should we do ?


## IN comes the sealedX library 

[SealedX](https://github.com/skydoves/sealedx) is a kotlin library by [github_skydoves](https://github.com/skydoves) 
that generates data classes based of a given input. This library helps us prevent those repetitive data classes by 
generating them for us at build time. The library uses KSP to generate the necessary code for us 

### How do we use it ? 
First of all make sure to follow the installation steps defined in the project [readme](https://github.com/skydoves/sealedx/blob/main/README.md)
As you may have already read, there are 2 annotations ``ExtensiveSealed`` and ``ExtensiveModel``

``ExtensiveSealed`` is an annotation that is used to anotate the sealed interface for our general  UI state 
The most common example something like this  
```angular2html
sealed interface UiState {
  data class Success(val data: Extensive) : UiState
  object Loading : UiState
  object Error : UiState
}

```
When fetching data for a screen these are usually the 3 states we work with

The `ExtensiveSealed` annotation usually takes an array of parameters for the classes that need UI state to be generated 
as shown below


```angular2html
@ExtensiveSealed(
    models = [
        ExtensiveModel(HomeScreen::class),


    ]
)
sealed interface UiState {
data class Success(val data: Extensive) : UiState
object Loading : UiState
object Error (val message:String): UiState


data class HomeScreen(
    val posts:List<Post>
)

```

Above the HomeScreen data class contains the expected data which is passed to the Extensive generic type
Note :  The keyword ``Extensive`` is from the library and is used as a placeholder for the given data class
provided in the models array.


The `ExtensiveModel` keyword takes a class definition as an argument and is passed 
as an element to the models arrays in the `ExtensiveSealed` annotation

So what this does it generates a sealed Interface for each `ExtensiveModel` declaration like below


```angular2html

public sealed interface HomeScreenUiState {
    public data class Error(
    public val message: String,
    ) : HomeScreenUiState

    public object Loading : HomeScreenUiState

    public data class Success(
        public val `data`: HomeScreen,
    ) : HomeScreenUiState
}
```

The name of the sealed interface will be name of the class passed to the `ExtensiveModel` appended with the name of the general
sealed interface e.g. HomeScreen + UiState = HomeScreenUiState

If you pass an array of 3 `ExtensiveModel` instances , 3 sealed interfaces will be generated
and so on 

Once you have declared you finshed declaring the `ExtensiveSealed` and  `ExtensiveModel`, its now time to generate them
To generate the classes , just rebuild your project and you will see the generated classes in your project 

### Let's start using our generated classes 

```angular2html


@HiltViewModel
class HomeScreenViewModel @Inject constructor(
    private val postRepository: PostRepository,
    ) : ViewModel() {

    private val _uiState = MutableStateFlow<HomeScreenUiState>(HomeScreenUiState.Loading)
    val uiState = _uiState.asStateFlow()


    init {
        getPosts()

    }


    private fun getPosts() {
        viewModelScope.launch {
            _uiState.value = HomeScreenUiState.Loading
            try {
                val response = productRepository.getAllProducts()
                _uiState.value = HomeScreenUiState.Success(data = HomeScreen(posts = response.posts))

            } catch (e: HttpException) {
                _uiState.value = HomeScreenUiState.Error(message = "Please check your internet connection")


            } catch (e: IOException) {
                _uiState.value = HomeScreenUiState.Error(message = "Server down please try again later")
            }
        }
    }

}
```

Above we just create our view model and fetch the data from our repository and store it into state 
We then consume our state in our composable like this 


```angular2html
fun HomeScreen(
navController: NavController,
navHostController: NavController,
viewModel: HomeScreenViewModel = hiltViewModel(),
) {
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    
    HomeScreenContent(uiState = uiState.value)

    


}

fun HomeScreenContent(
    uiState:HomeScreenUiState
){
    Box(modifier = Modifier.fillMaxSize()) {
        when (uiState) {
            is ProductsUiState.Loading -> {
                // show something when loading
            
    
            }
            is ProductsUiState.Error -> {
                // show the error message
    
    
            }
            is ProductsUiState.Success -> {
                // show the posts
    
            }
                    
        }
    }
}


```

Above we just consume the data and show something based on the current state
I am using 2 composable. The reason is explained in this previous article 
[here](https://medium.com/@peterkagure/stateful-and-stateless-components-in-jetpack-compose-18f89f790750) 

### Limitations of this library

* Currently, the library has a bug where when you try to define more than one sealed interfaces with the
  library it will append combine all the states from all the sealed interface instances annotated with the `ExtensiveSealed`
  keyword into the generated files hence it will cause a compilation error if you declare the same state in more than one instance
    I opened an issue in the repo hoping it will be fixed but until then try to only declare one instance annotated with the `ExtensiveSealed`

N/B: Note this is only a solution for when it comes to data fetching for multiple screens. If your app has complex states 
then this might not be ideal for your project 


Thank you for reading and I hope you learnt something new until next time .....Bye see you next time