Home > Back-end >  Generic Composables
Generic Composables

Time:11-24

I wrote a simple wrapper for the Text composable to use a string resource.

@Composable
fun Text(@StringRes id: Int) = Text (stringResource(id))

Next I did the same for bold text.

val bold = TextStyle(fontWeight = FontWeight(600))

@Composable
fun Bold (text: String) = Text (style = bold, text = text)

@Composable
fun Bold (@StringRes id: Int) = Bold (stringResource(id))

Now I realized that the two composables using the string resource look quite the same. So I tried to make them generic. But this does not work:

@Composable
fun <C: Composable>WithStrRes (@StringRes id: Int) = C(stringResource(id))

The error is:

Type parameter C cannot be called as function

How to fix this?

CodePudding user response:

Compiler sees C as a type, therefore it cannot be invoked as a function. Function inheritance is not available in Kotlin, you can use overriding or overloading for this purposes, but in case of Compose I suggest the latter, since it is more declarative and compose-style like.

@Composable
fun withStrRes(
    @StringRes id: Int, 
    fStyle: FontStyle, // for italic or normal e.g. FontStyle.Italic
    fWeight: FontWeight, // for bold or normal e.g. FontWeight.Bold
    ...
) {
    Text(stringResource(id), fontStyle = fStyle, fontWeight = fWeight)
}

The main idea of compose to declare elements easily and quickly, and then read the code for UI easily and quickly. I think your best bet for Text would be to use default Text composable, since stringResource(id) is not that long to write.

Use your own composable methods for complex multi-element designs.

CodePudding user response:

The thing you are trying to achieve is not possible in kotlin. You are trying to give type parameter C extending Composable. Since compose is based on kotlin functions (Not the classes) , inheritance is the not possible for functions. Also function cannot be passed as type parameter in kotlin. Only classes can be added as type parameters.

Here the Composable is an annotation class. If you define like below only Composable class can be passed as type. Annotation classes are final and it is not possible to create another class extending Composable class.

@Composable
fun <C: Composable>WithStrRes (@StringRes id: Int)

So your definition only allows to call function like below which is unwanted in compose.

  @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        WithStrRes<Composable>(id = R.string.app_name) 
      //Here type param should be Composable class itself. This is not required 
    }

The way to achieve reusable composables is by following way definining higher order functions of @Composable type as reuse below.

Here second parameter accepts only functions which is annotated with @Composable

Generic Composable:

@Composable
fun Bold(text: String) = Text(style = bold, text = text)

@Composable
fun Bold(@StringRes id: Int) = Bold(stringResource(id))

@Composable
fun WithStrRes(@StringRes id: Int, C: @Composable (id: Int) -> Unit) {
    C(id)
}

Usage:

 @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        StackOverflowAndroidTheme {
            Greeting("Android")
        }
        WithStrRes(id = R.string.app_name) {
            Bold(id = it)
        }
    }
  • Related