JabKnowsNothing
JabKnowsNothing

Reputation: 969

Android Compose How to Fix "ComposableModifierFactory" and "UnnecessaryComposedModifier" Lint Warnings?

How can I create reusable modifiers without android compose lint rules throwing a fit?

I don't want to have to copy/paste the same modifiers for every screen within my app, I would rather just create an extension function I can call like this,

    Box(modifier = Modifier.defaultFillScreen())

But that extension function, shown below, keeps throwing lint errors.

@Composable
fun Modifier.defaultFillScreen() = this.then(Modifier
  .fillMaxWidth()
  .navigationBarsWithImePadding()
  .verticalScroll(rememberScrollState())
  .padding(dimensionResource(id = R.dimen.standard_padding)))

Gives me the following lint error:

ComposableModifierFactory: Modifier factory functions should not be marked as @Composable, and should use composed instead

When I make that change I then get a new lint error:

fun Modifier.defaultFillScreen() = composed { this.then(Modifier
  .fillMaxWidth()
  .navigationBarsWithImePadding()
  .verticalScroll(rememberScrollState())
  .padding(dimensionResource(id = R.dimen.standard_padding))) }

UnnecessaryComposedModifier: Unnecessary use of Modifier.composed

How can I create a reusable modifier without compose complaining about it? Writing the same 5 lines of modifier code for every screen is not an acceptable answer.

dependencies:

'androidx.activity:activity-compose:1.3.1', 
'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07', 
'androidx.compose.material:material:1.0.5',
'androidx.navigation:navigation-compose:2.4.0-alpha06',
'androidx.compose.ui:ui:1.0.5',
'androidx.compose.ui:ui-tooling:1.0.5'

Android studio:

Android Studio Arctic Fox | 2020.3.1 Build #AI-203.7717.56.2031.7583922, built on July 26, 2021

Upvotes: 2

Views: 2849

Answers (1)

Thracian
Thracian

Reputation: 67443

I don't see the same warning with compose 1.2.0-alpha and Android Studio Bubblebee, it used to appear when i use Modifier.composed without state.

Purpose of Modifier.composed is having stateful modifiers which you use with remember, LaunchedEffect. When you don't have a state associated with your Modifier you should you Modifier.then instead

fun Modifier.composedBackground(width: Dp, height: Dp, index: Int) = composed(
    // pass inspector information for debug
    inspectorInfo = debugInspectorInfo {
        // name should match the name of the modifier
        name = "myModifier"
        // add name and value of each argument
        properties["width"] = width
        properties["height"] = height
        properties["index"] = index
    },
    // pass your modifier implementation that resolved per modified element

    factory = {

        val density = LocalDensity.current

        val color: Color = remember(index) {
            Color(
                red = Random.nextInt(256),
                green = Random.nextInt(256),
                blue = Random.nextInt(256),
                alpha = 255
            )
        }

        // 🔥 Without remember this color is created every time item using this modifier composed
//        val color: Color = Color(
//            red = Random.nextInt(256),
//            green = Random.nextInt(256),
//            blue = Random.nextInt(256),
//            alpha = 255
//        )

        // add your modifier implementation here
        Modifier.drawBehind {

            val widthInPx = with(density) { width.toPx() }
            val heightInPx = with(density) { height.toPx() }

            drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
        }
    }
)

This is just a sample composed example. If you change from remember you will see that at each recomposition random color will change.

And without composed it will give error @Composable invocations can only happen from the context of a @Composable function if you use remember like the snippet below.

fun Modifier.nonComposedBackground(width: Dp, height: Dp, index: Int) = this.then(

    // add your modifier implementation here
    Modifier.drawBehind {
        val color: Color = remember(index) {
            Color(
                red = Random.nextInt(256),
                green = Random.nextInt(256),
                blue = Random.nextInt(256),
                alpha = 255
            )
        }

        val widthInPx = width.toPx()
        val heightInPx = height.toPx()

        drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
    }
)

Upvotes: 2

Related Questions