ceving
ceving

Reputation: 23824

Generic Composables

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?


Using function references does not work either. When I try to replace the redundant definitions with a builder

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

fun withStrRes(composable: (String) -> Unit): (Int) -> Unit =
    @Composable
    fun (@StringRes id: Int) {
        composable(stringResource(id))
    }

val Text: (id: Int) -> Unit = withStrRes(::Text)
val Bold: (id: Int) -> Unit = withStrRes(::Bold)

I get the error

Function References of @Composable functions are not currently supported

Upvotes: 3

Views: 1812

Answers (1)

Gowtham K K
Gowtham K K

Reputation: 3429

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)
        }
    }

Upvotes: 1

Related Questions