Ali_Waris
Ali_Waris

Reputation: 2382

Chaining Modifier based on certain conditions in Android Compose

I want to apply modifier in such a way that if the width is provided, it should use the provided width, else use the max-width available.

I am applying the Modifier in the below fashion, but the result is not as expected. The view width is going haywire. Requesting guidance here.

val myModifier = Modifier.padding(
    start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp
)

if (viewWidth == null)
    myModifier.then(Modifier.fillParentMaxWidth(1f))
else
    myModifier.then(Modifier.width(viewWidth))

myModifier.then(
    Modifier.height(viewHeight ?: 100.dp)
        .clickable(onClick = { listener.onItemClick(item) })
)

Upvotes: 60

Views: 28956

Answers (7)

Ayush
Ayush

Reputation: 141

Found another method which I feel is cleaner. Say you want to add a "invoke" to the modifier based on a condition, you can use -

Modifier
    .let { if (condition) it.invoke() else it}

Upvotes: 5

Sean
Sean

Reputation: 3015

I'm giving my answer based on my own implementation based on https://proandroiddev.com/jetpack-compose-tricks-conditionally-applying-modifiers-for-dynamic-uis-e3fe5a119f45

There's nothing Modifier-specific here. This idea can be generalized to any class.

Generic functions

inline fun <R : Any> R.applyWhen(
    condition: Boolean,
    block: R.() -> R,
): R = applyChoice(condition = condition, trueBlock = block, falseBlock = { this })

inline fun <R : Any> R.applyChoice(
    condition: Boolean,
    trueBlock: R.() -> R,
    falseBlock: R.() -> R,
): R {
    return if (condition) {
        trueBlock()
    } else {
        falseBlock()
    }
}

Usage

val myModifier = Modifier
    .padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)
    .applyChoice(viewWidth == null, { fillParentMaxWidth(1f) }, { width(viewWidth!!) })
    .height(viewHeight ?: 100.dp)
    .clickable(onClick = { listener.onItemClick(item) })

Upvotes: 1

Jeffrey Liu
Jeffrey Liu

Reputation: 1129

Just in case if you need to run composable

@Composable
fun Modifier.conditional(condition: Boolean, modifier: @Composable Modifier.() -> Modifier) =
    then(if (condition) modifier.invoke(this) else this)

Upvotes: 3

Oliver Metz
Oliver Metz

Reputation: 3748

You can create a conditional modifier via an extension function:

fun Modifier.conditional(condition : Boolean, modifier : Modifier.() -> Modifier) : Modifier {
    return if (condition) {
        then(modifier(Modifier))
    } else {
        this
    }
}

This lets you chain a modifier in a conditional block like this:

val applySpecialBackground : Boolean = [...]
Column(
    modifier = Modifier
        .fillMaxWidth()
        .conditional(applySpecialBackground) {
            background(Color.Red)
        }
        .padding(16.dp)

) { [...] }

It will only apply the conditional modifier when the condition is true.

If you need a modifier for the false case just chain a second negated conditional.

val applySpecialBackground : Boolean = [...]
Column(
    modifier = Modifier
        .fillMaxWidth()
        .conditional(applySpecialBackground) {
            background(Color.Red)
        }
        .conditional(!applySpecialBackground) {
            background(Color.Blue)
        }
        .padding(16.dp)

) { [...] }

If you're feeling super fancy you can also add an optional negative case to the extension function.

fun Modifier.conditional(
    condition: Boolean,
    ifTrue: Modifier.() -> Modifier,
    ifFalse: (Modifier.() -> Modifier)? = null
): Modifier {
    return if (condition) {
        then(ifTrue(Modifier))
    } else if (ifFalse != null) {
        then(ifFalse(Modifier))
    } else {
        this
    }
}

This will give you a separate parameter for the case that your condition is false:

val applySpecialBackground : Boolean = [...]
Column(
    modifier = Modifier
        .fillMaxWidth()
        .conditional(
            applySpecialBackground,
            ifTrue = { background(Color.Red) },
            ifFalse = { background(Color.Blue) }
        )
        .padding(16.dp)

) { [...] }

Big thanks to mtotschnig for pointing out a critical bug in the previous implementation!

Upvotes: 86

M.Muzammil
M.Muzammil

Reputation: 683

All the above answers are good enough but i would like to add my one cent. I found this snippet more readable and clean.

   ClickableText(
                modifier = Modifier
                    .let {
                        if (selectedPosition == index) {
                            return@let it
                                .background(
                                    Green200,
                                    shape = RoundedCornerShape(12.dp)
                                )

                        }
                        it
                    }
                    .padding(horizontal = 12.dp, vertical = 4.dp),
                text = AnnotatedString(categories[index]),
                style = TextStyle(
                    fontSize = 14.sp,
                    textAlign = TextAlign.Center
                ),
                onClick = {
                    selectedPosition = index
                }
            )

Upvotes: 8

okacat
okacat

Reputation: 596

You can also use Modifier.then in a more compact way:

val modifier = Modifier
            .padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)
            .then(if(viewWidth == null) Modifier.fillMaxWidth(1f) else Modifier.width(viewWidth))
            .height(viewHeight ?: 100.dp)
            .clickable(onClick = { listener.onItemClick(item) })

See: https://jetc.dev/slack/2020-12-13-conditional-modifiers.html

Upvotes: 28

Avijit Karmakar
Avijit Karmakar

Reputation: 9388

Modifier has a then function to concatenate the current modifier with another modifier. This then function returns a new modifier that you have not used it. You have to re-initialize your myModifier variable with the returned modifier.

Check the below code:

var myModifier = Modifier.padding(
    start = 4.dp, end = 4.dp, top = 8.dp, bottom = 8.dp
)

if (viewWidth == null)
  myModifier = myModifier.then(Modifier.fillParentMaxWidth(1f))
else
  myModifier = myModifier.then(Modifier.width(viewWidth))

myModifier = myModifier.then(
  Modifier
    .height(viewHeight ?: 100.dp)
    .clickable(onClick = { listener.onItemClick(item) })
)

Upvotes: 31

Related Questions