Stateful and stateless components in Jetpack compose
Published: 2023-04-30 14:08:02



#### Keep 2 versions of the same composable:Stateless and Stateful

#### What are stateful and stateless composables in Jetpack compose ? 

A stateless component is a component that doesn't maintain any internal state. 
It's purely a function of its inputs, meaning that the output of the component is solely determined by the props that are passed to it. 
These components are also known as "dumb components" because they don't have any logic beyond rendering what 
they're given.

On the other hand, a stateful component is a component that does maintain an internal state.
It's a function of its inputs as well as its own internal state. 
Stateful components are also known as "smart components" because they have 
logic beyond rendering what they're given. They can respond to user events, 
perform complex calculations, and modify their own state, which can trigger a re-render of the component.

##### An example of a stateful Composable

```
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

@Composable
fun HomeScreen(
    viewModel:HomeScreenViewModel = hiltViewModel(),
) {
    val posts = viewModel.posts.collectAsStateWithLifecycle()
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ){
        items(items =posts){ post -> 
            Post(post = post)
        }
    }
}

```

In the 1st composable the state is stored in the composable.
This is said to be stateful since the composable stores its current state

In the 2nd composable the state is stored in a viewModel. Normally this is what you
usually do in  project 

While this approach works fine there are a few issues that arise 
They include


* Testing: Testing stateful composables can be more difficult because they require you to manage and manipulate their internal state. This can make it harder to write tests that are reliable and cover all possible scenarios.

* They break previews: Stateful composables cannot be easily previewed in 
development mode since they for them to be initialized the need a customview model instance
which will in turn lead to a custom repository for the view model and custom mock data 
for the data sources (local or remote etc)


#### What is the solution then  ?

In comes stateless composables. As discussed earlier stateless composables do not manage state they simply get 
state passed on to them. When the UI needs to make changes we can pass them to the necessary functions as parameters

##### But we will need to eventually instantiate the view model right ?

Yes. We will need 2 different versions of the same composable, A stateless and stateful one then 
we use the stateless one for testing and previews.

Here's an example

##### An example of a stateful Composable

```
//Stateful version of the composable
@Composable
fun HomeScreen(
    viewModel:HomeScreenViewModel = hiltViewModel(),
) {
    val posts = viewModel.posts.collectAsStateWithLifeCycle()
    HomeScreenContent(posts = posts.value)
}

//Stateless version of the composable
@Composable
fun HomeScreenContent(
    posts:List<Post>
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ){
        items(items = posts){ post -> 
            Post(post = post)
        }
    }
}

```

Above is the stateful and stateless version of the composable. It a good practice to prefix 
the stateless composable with "Content" then pass it to the stateful version and pass the state
and the state modifiers functions for example in this case you could add a like post function and pass its 
implementation to the HomeScreenContent composable.

NB: This should only to apply to screen level composables since the other composables in the 
screen composable are already statelss 


I recently learnt about this new concept and decided to share it 
For more examples check how it was implemented in the following repos

* [Expense Tracker App](https://github.com/chege4179/ExpenseTrackerApp)
* [Now In Android](https://github.com/android/nowinandroid)


Philip Lackner also did a video addressing the same issue 
[Video Link](https://youtu.be/4D79It7Jnzg)


I hope this article helped you in a way :)