@Composable Modifier vs composed factory

@Composable Modifier vs composed factory

Composed and CMF (@Composable Modifier Factory) are two methods to create custom modifiers in Jetpack Compose. They both enable using higher-level compose APIs like animate*AsState and holding state with the remember() function.

// @Composable Modifier Factory
@Composable
fun Modifier.fade(enable: Boolean): Modifier {
    val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)
    return graphicsLayer { this.alpha = alpha }
}

// composed {}
fun Modifier.fade(enable: Boolean): Modifier = composed {
    val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)
    graphicsLayer { this.alpha = alpha }
}        

Google initially suggested using composed over Composable Modifier Factory (CMF), even warning users in Android Studio. However, they've recently updated their recommendations, advising the use of CMF or Modifier.node due to performance concerns with composed.

We'll examine the distinctions, suitable scenarios, constraints, and performance of each method.

1. Extractability

To boost performance, we can move our modifiers outside of the Composition scope. This helps avoid the overhead of constructing them during each recomposition, particularly when using animations or withLazyColumn/LazyRow items.

val extractedModifier = Modifier.background(Color.Navy).padding(8.dp)...

@Composable
fun MyComposable() {
    LazyColumn {
        items(5) { Text("Hello $it", modifier = extractedModifier) }
    }
}        
@Composable
fun Modifier.usingComposableFactory(): Modifier = ...

fun Modifier.usingComposed(): Modifier = composed {/***/}

// usingComposed can be used outisde the Composition scope
val extractedModifier = Modifier.usingComposed()

@Composable
fun MyComposable() {
    ...
    // we can only use usingComposableFactory() inside a @Composable scope
    Text("Hello $it", modifier = extractedModifier.usingComposableFactory())
}        

2- Resolution Location of CompositionLocal Values

When using CompositionLocals such as LocalContentColor, CMF and composed behave differently.

  1. CMF: CompositionLocals values are resolved at the call site of the modifier factory.
  2. composed: CompositionLocals values are resolved at the usage site of the composed factory.

Like....

import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.graphics.*

@Composable
fun Modifier.myCMFBackground(): Modifier {
    val color = LocalContentColor.current
    return this.then(background(color.copy(alpha = 0.5f)))
}

fun Modifier.myComposedBackground(): Modifier = composed {
    val color = LocalContentColor.current
    this.then(background(color.copy(alpha = 0.5f)))
}

@Composable
fun MyScreen() {
    val greenColor = remember { Color.Green }
    val redColor = remember { Color.Red }

    CompositionLocalProvider(LocalContentColor provides greenColor) {
        val usingCMFModifier = remember { Modifier.myCMFBackground().size(16.dp) }
        val usingComposedModifier = remember { Modifier.myComposedBackground().size(16.dp) }

        CompositionLocalProvider(LocalContentColor provides redColor) {
            Row {
                // Box will have green background, not red as expected.
                Box(modifier = usingCMFModifier)
                
                // Box has green background as expected.
                Box(modifier = usingComposedModifier)
            }
        }
    }
}
        


Laxmi kant
output.

3. State Resolution .

A. CMF

B Normal

when we have some lists and need to render then we need to save the composable state.

fun Modifier.rotateOnClick() = composed {
    val color = remember { mutableStateOf(listOf(Color.Red, Color.Green).random()) }
    var isClicked by remember { mutableStateOf(false) }
    val rotation by animateFloatAsState(targetValue = if (isClicked) 45f else 0f)

    background(color = color.value)
        .clickable { isClicked = !isClicked }
        .graphicsLayer { rotationZ = rotation }
}

@Composable
fun Modifier.rotateOnClick(): Modifier {
   // same as rotateOnClickUsingComposed...
}        

without saved state of each composable

@Composable
fun BoxesRow() {
    LazyRow {
        items(10) {
            Box(
                modifier = Modifier.rotateOnClick().size(100.dp),
            )
        }
    }
}        

You will notice a strange behavior…

CMF


It's indeed peculiar! CMF resolves the state only once at the call site, while composed resolves the state at the usage site for each layout. This difference is crucial to keep in mind when designing reusable custom Modifiers.

4. Performance

It's not as simple as just avoiding CMF altogether. Even though composed used to work fine by calling materialize(), it's now discouraged due to performance issues.

The problem is, calling materialize() is expensive. Even a basic modifier can involve lots of other modifiers and states. Flattening all of these out affects performance and might create redundant copies of Modifier.Elements.

For example, before migrating to the Modifier. Node API, just the clickable modifier would add all these modifiers to the layout.


Compose

- 13 Modifier.composed calls

- 34 remember calls

- 11 Side Effects

- 16 Leaf Modifier.Elements

Stay tuned for upcoming articles. For any quires or suggestions, feel free to hit me on Twitter Insta + LinkedIn





要查看或添加评论,请登录

Laxmi Kant的更多文章

  • StateFlow vs SharedFlow in?kotlin

    StateFlow vs SharedFlow in?kotlin

    Before we Discuss about Stateflow vs SharedFlow let’s have look about the Kotlin flow ??? ???????? ???? ?? ????????? So…

  • Internet Connection State in Android apps

    Internet Connection State in Android apps

    Internet connection sometimes can be crucial to your applications. You can inform a user with the banner stating that…

  • Android Context

    Android Context

    What is Context ? and how is it used? A Context is a handle to the system; It provides services like resolving…

  • Activity Launch Mode

    Activity Launch Mode

    Launch mode is an instruction for Android OS which specifies how the activity should be launched. It instructs how any…

    1 条评论

社区洞察

其他会员也浏览了