Organizing large Jetpack Compose code bases. My best version yet
Tags: android, kotlin, mobile, tristan
Published: 2024-03-14 23:59:15

### Table of contents
1. [Too long didn't read. Give me the code!!!](#tldr)
2. [Resources ](#res)
3. [The most basic of organizations](#group)
4. [Does this work with Recomposition?](#recomp)
5. [Scoping to enforce Compose types](#scope)
6. [Defining the Scopes](#def)

### My app on the Google play store
- [The app](https://play.google.com/store/apps/details?id=elliott.software.clicker)


### Resources <a name="res"></a>
- [BumbleTech design systems](https://medium.com/bumble-tech/refining-compose-api-for-design-systems-d652e2c2eac3)


### Too long didn't read (TLDR) <a name="tldr"></a>
- Essentially, we are trying to create a `design system` that we can implement across our compose code base
- step 1: organize your composables by wrapping them like this:
```
object SharedComponents {
@Composable
    fun TooLongDidNotRead(){
        Text("No time to read! I need the code now!")
    }

}

```

- step 2: Implement [Scoping](https://medium.com/bumble-tech/refining-compose-api-for-design-systems-d652e2c2eac3) to enforce what type of composables go where

###GitHub code
- [My GitHub code for scoping](https://github.com/thePlebDev/Clicker/blob/5b827944aa9075ee3a6b08a15ba0081532b5d94d/app/src/main/java/com/example/clicker/presentation/sharedViews/SharedScopes.kt#L49)
- [My GitHub code for organized components](https://github.com/thePlebDev/Clicker/blob/5b827944aa9075ee3a6b08a15ba0081532b5d94d/app/src/main/java/com/example/clicker/presentation/sharedViews/SharedComponents.kt#L22)
- [The wiki for my code](https://github.com/thePlebDev/Clicker/wiki)



### The most basic of organizations | Grouping <a name="group"></a>
- If you find your Jetpack Compose code confusing to understand and copy and pasting is faster than reusing, then you might need to do some reorganization.
- The most basic of Jetpack compose organization is what I call `grouping`. Essentially, grouping takes place when you use Kotlin's [object declaration](https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview) to group similar jetpack compose components together. Like so,

```kotlin
/**
 * SharedComponents represents the most used and most stable versions of components used throughout this application
 * **/
object SharedComponents {

    /**
     * very fancy text*/
    @Composable
    fun FancyText(){

    }
    
    /**Less fancy text*/
    @Composable
    fun NormalText(){
        
    }

}
// how to use
SharedComponents.FancyText()


```

- While this reorganization seems trivial at first glance is really does give you a better understanding of what composables are from where.

### Does this work with Recomposition? <a name="recomp">
- So you are wondering if this will still allow compose to do recomposition even without the `@Stable` annotation. The answer is.... `I think so`. I tested it with this code:
```kotlin
var timesClicked by remember { mutableStateOf(0) }
    Testing.Combined(
        timesClicked.toString(),
        onClick = {timesClicked++}
    )

object Testing{
    @Composable
    fun Combined(
        timesClicked:String,
        onClick:()->Unit,
    ){

        Column() {
            RecomposeEveryTime(
                timesClicked,
                onClick = {onClick()}
            )
            DontReCompose()
        }
    }

    @Composable
    fun RecomposeEveryTime(
        timesClicked:String,
        onClick:()->Unit,
    ){
        Log.d("TESTINGRECOMPOSE","RecomposeEveryTime")

        Button(onClick ={onClick()}) {
            Text(
                timesClicked,
                fontSize = 30.sp,
                color = Color.Red
            )
        }
    }
    @Composable
    fun DontReCompose(){
        Log.d("TESTINGRECOMPOSE","DontReCompose ")
        Text(
            "NO RECOMPOSITION",
            fontSize = 30.sp,
            color = Color.Red
        )
    }
}

```
- Judging from the log statements, only the `RecomposeEveryTime()` is recomposing (which is the desired effect) and `DontReCompose()` is not. So from these tests I can proudly say, I think recomposition works as intended


### Scoping to enforce Compose types <a name="scope">
- Read the original [Bumbletech article](https://medium.com/bumble-tech/refining-compose-api-for-design-systems-d652e2c2eac3) if you want a better and deeper understanding of the Scoping principle
- [My GitHub code for scoping](https://github.com/thePlebDev/Clicker/blob/5b827944aa9075ee3a6b08a15ba0081532b5d94d/app/src/main/java/com/example/clicker/presentation/sharedViews/SharedScopes.kt#L49)
- [My GitHub code for organized components](https://github.com/thePlebDev/Clicker/blob/5b827944aa9075ee3a6b08a15ba0081532b5d94d/app/src/main/java/com/example/clicker/presentation/sharedViews/SharedComponents.kt#L22)

- Essentially, we can use Scoping when we want to use strong typing to enforce what type of Composables goes where. Here is a basic example:
```kotlin

    @Composable
    fun NoDrawerScaffold(
        topBar:@Composable ScaffoldTopBarScope.() -> Unit,
        bottomBar:@Composable ScaffoldBottomBarScope.() -> Unit,
        content:@Composable (contentPadding: PaddingValues,) -> Unit,

        ) {
        val topBarScaffoldScope = remember(){ScaffoldTopBarScope(35.dp)}
        val bottomBarScaffoldScope = remember(){ScaffoldBottomBarScope(25.dp)}

        Scaffold(
            containerColor = MaterialTheme.colorScheme.primary,
            topBar = {
                Row(modifier = Modifier
                    .fillMaxWidth()
                    .background(MaterialTheme.colorScheme.primary)){
                    with(topBarScaffoldScope){
                        topBar()
                    }
                }

            },
            bottomBar = {
                with(bottomBarScaffoldScope){
                    bottomBar()
                }
            },

            ) { contentPadding ->
            content(contentPadding)

        }
    }

```
- The composable function above uses `ScaffoldTopBarScope` and `ScaffoldBottomBarScope` to only allow composables of those instances to be allowed.
- If you are wondering why we have to define an instance of the scope to first use it, like so: 
```kotlin
with(topBarScaffoldScope){
                        topBar()
                    }

```
- It is because of the way that extention functions work in kotlin. ` ScaffoldTopBarScope.() -> Unit` defines a extension function on an instance of `ScaffoldTopBarScope`. So to be able to properly use topBar() we need an instance of  ScaffoldTopBarScope

### Defining the Scopes <a name="def">
- When defining scopes we need to do so in a very particular way:
```kotlin
@Stable
class ScaffoldBottomBarScope(
    private val iconSize: Dp, 
){
@Composable
    fun DualButtonNavigationBottomBarRow(){
         // composable function 
            Icon(
            imageVector = imageVector,
            contentDescription = contentDescription,
            tint = color,
            modifier = Modifier.size(iconSize)
        )
}

}

```
- Notice the `@Stable` annotation. This is done because of how Jetpack compose defines [Stability](https://developer.android.com/jetpack/compose/performance/stability). Long story short, `@Stable` allows recomposition to work as it is intended to 


###Please read the original Bumble tech post 
- If you are really looking to find a better organization stragety for your jetpack compose code, then please read the original [Bumble tech article](https://medium.com/bumble-tech/refining-compose-api-for-design-systems-d652e2c2eac3) on this topic


### Conclusion 
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).