Think Once, Reuse Forever: Leveraging Generic Jetpack Composables to Eliminate Duplicate Code

The use of generic programming in Jetpack Compose promotes the creation of highly reusable designs and, furthermore, avoids one of the less desired code smells: duplicate code.

We will see a brief and simple example of how to avoid duplicate code using generics.

Consider the following models in our project

data class Beer(
    val name: String = "",
    val alcoholByVolume: Double = 0.0,
    val price: Double = 0.0
)

data class Coffee(
    val name: String = "",
    val price: Double = 0.0,
    val extras: String = ""
)

data class Soda(
    val name: String = "",
    val price: Double = 0.0,
    val sugarGrams: Double = 0.0
)

We want to display a list of each model. A naive approach would be create a list of each model. Something like this.

// BEERS
@Composable
fun ListBeers(
    items: List<Beer>,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        this.items(
            items = items,
            itemContent = {
                ElevatedCard(modifier = Modifier.padding(10.dp)) {
                    Column(
                        modifier = Modifier.padding(10.dp)
                    ) {
                        Text(
                            text = it.name,
                            fontWeight = FontWeight.Bold,
                            color = MaterialTheme.colorScheme.primary
                        )

                        Divider()

                        Column (
                            modifier = Modifier.padding(start = 20.dp)
                        ){
                            Text("Name: " + it.name)
                            Text("Alcohol: " + it.alcoholByVolume)
                            Text("Price " + it.price)
                        }
                    }
                }
                Spacer(modifier = Modifier.height(5.dp))
            }
        )
    }
}


// COFFEES
@Composable
fun ListCoffees(
    items: List<Coffee>,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        this.items(
            items = items,
            itemContent = {
                ElevatedCard(
                    modifier = Modifier.padding(10.dp),
                ) {
                    Column(
                        modifier = Modifier.padding(10.dp)
                    ) {
                        Text(
                            text = it.name,
                            fontWeight = FontWeight.Bold,
                            color = MaterialTheme.colorScheme.primary
                        )

                        Divider()

                        Column (
                            modifier = Modifier.padding(start = 20.dp)
                        ){
                            Text("Name: " + it.name)
                            Text("Price: " + it.price)
                            Text("Extras: " + it.extras)
                        }

                    }
                }
                Spacer(modifier = Modifier.height(5.dp))
            }
        )
    }
}


// SODAS
@Composable
fun ListSodas(
    items: List<Soda>,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        this.items(
            items = items,
            itemContent = {
                ElevatedCard(modifier = Modifier.padding(10.dp)) {
                    Column(
                        modifier = Modifier.padding(10.dp)
                    ) {
                        Text(
                            text = it.name,
                            fontWeight = FontWeight.Bold,
                            color = MaterialTheme.colorScheme.primary
                        )

                        Divider()

                        Column (
                            modifier = Modifier.padding(start = 20.dp)
                        ){
                            Text("Name: " + it.name)
                            Text("Price: " + it.price)
                            Text("Sugar Grams: " + it.sugarGrams)
                        }

                    }
                }
                Spacer(modifier = Modifier.height(5.dp))
            }
        )
    }
}

The lists look like this:

As you can see, the lists are very similar; they share the same structure, title, and content details. Therefore, we can remove a lot of duplicate code by creating a generic list.

We will send the common elements (title and content details) as parameters, and we will use generics to specify the type.

@Composable
fun <T: Any> GenericList(
    items: List<T>,
    title: String,
    contentDetails: @Composable (T) -> Unit,
) {

}

And we use the title and content

@Composable
fun <T: Any> GenericList(
    items: List<T>,
    title: String,
    contentDetails: @Composable (T) -> Unit,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        this.items(
            items = items,
            itemContent = {
                ElevatedCard(modifier = Modifier.padding(10.dp)) {
                    Column(
                        modifier = Modifier.padding(10.dp)
                    ) {
                        Text(
                            text = title, // Title
                            fontWeight = FontWeight.Bold,
                            color = MaterialTheme.colorScheme.primary
                        )

                        Divider()

                        Column (
                            modifier = Modifier.padding(start = 20.dp)
                        ){
                            contentDetails(it) // Content Details
                        }

                    }
                }
                Spacer(modifier = Modifier.height(5.dp))
            }
        )
    }
}

Now, see how clean the new lists are

// BEERS
@Composable
fun NewBeersList(items: List<Beer>) {
    GenericList( items,"Beers",
        contentDetails = {
            Text("Name: " + it.name)
            Text("Alcohol: " + it.alcoholByVolume)
            Text("Price " + it.price)
        }
    )
}

// COFFEES
@Composable
fun NewCoffeesList(items: List<Coffee>) {
    GenericList(items,"Coffees",
        contentDetails = {
            Text("Name: " + it.name)
            Text("Price: " + it.price)
            Text("Extras: " + it.extras)
        }
    )
}

// SODAS
@Composable
fun NewSodasList(items: List<Soda>) {
    GenericList(items,"Sodas",
        contentDetails = {
            Text("Name: " + it.name)
            Text("Price: " + it.price)
            Text("Sugar Grams: " + it.sugarGrams)
        }
    )
}

Leave a Reply

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