Booger
Booger

Reputation: 18725

Applying Material3 colors to OutlinedTextField in Jetpack Compose

EDIT: Adding more detail - to make code complete:

I am having challenges applying theme colors to OutlinedTextFields, after converting to the new M3 themes.

From what I can tell, there is no direct support for using the new design token colors (primaryContainer, tertiary, etc) when using the OutlinedTextField.

Question: Is there a simple way to apply these colors universally?
Question: When I apply custom colors, they aren't all applied, and I am not sure why?

What I tried:

This is the function I use to create custom TextFieldColors, and how I apply them:

@Composable
fun customTextColors(): TextFieldColors =
    TextFieldDefaults.outlinedTextFieldColors(
        textColor = MaterialTheme.colorScheme.onBackground,
        disabledTextColor = MaterialTheme.colorScheme.onBackground,
        backgroundColor = MaterialTheme.colorScheme.background,
        cursorColor = MaterialTheme.colorScheme.onBackground,
        errorCursorColor = MaterialTheme.colorScheme.error,
        focusedBorderColor = MaterialTheme.colorScheme.primary,
        unfocusedBorderColor = MaterialTheme.colorScheme.onBackground,
        disabledBorderColor = MaterialTheme.colorScheme.onBackground,
        errorBorderColor = MaterialTheme.colorScheme.error,
        leadingIconColor = MaterialTheme.colorScheme.onBackground,
        disabledLeadingIconColor = MaterialTheme.colorScheme.onBackground,
        errorLeadingIconColor = MaterialTheme.colorScheme.error,
        trailingIconColor = MaterialTheme.colorScheme.onBackground,
        disabledTrailingIconColor = MaterialTheme.colorScheme.onBackground,
        errorTrailingIconColor = MaterialTheme.colorScheme.error,
        focusedLabelColor = MaterialTheme.colorScheme.primary,
        unfocusedLabelColor = MaterialTheme.colorScheme.onBackground,
        disabledLabelColor = MaterialTheme.colorScheme.onBackground,
        errorLabelColor = MaterialTheme.colorScheme.error,
        placeholderColor = MaterialTheme.colorScheme.onBackground,
        disabledPlaceholderColor = MaterialTheme.colorScheme.primary,
    )

then applying them in this way:

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.input.*
import com.myApp.demo.R
import com.myApp.demo.data.StaticData
import com.myApp.demo.ui.theme.card_corner_radius
import com.myApp.demo.ui.theme.grey_400
import com.myApp.demo.ui.theme.horizfull_verthalf

@Composable
fun NameTextInput(name: String, onNameInfoValid: (Boolean) -> Unit) {
    // Name
    val nameState = remember { mutableStateOf(TextFieldValue(name)) }
    val nameString = stringResource(R.string.name)
    val nameLabelState = remember { mutableStateOf(nameString) }
    val isNameValid = if (nameState.value.text.length >= 5) {
        nameLabelState.value = nameString
        onNameInfoValid(true)
        true
    } else {
        nameLabelState.value = stringResource(R.string.name_error)
        onNameInfoValid(false)
        false
    }
    OutlinedTextField(
        shape = RoundedCornerShape(card_corner_radius),
        value = nameState.value,
        singleLine = true,
        onValueChange = { if (it.text.length <= 30) nameState.value = it },
        isError = !isNameValid,
        label = { Text(nameLabelState.value) },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Text,
            capitalization = KeyboardCapitalization.Words
        ),
        colors = customTextColors(),  // <-- Here is where colors are set!
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizfull_verthalf)
    )
}

They aren't all being applied.

I see the focusedBorderColor set correctly, but I don't see focusedLabelColor being applied correctly (in this case RED is the correct primary color to be applied, so I would want to see the Label also be RED)

outlined text field with focused border set

Also error is border is working, but not for the other attributes:

error text view

Here is my Theme setup:

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable

private val DarkColorPalette = darkColorScheme(
    primary = grey_700,
    onPrimary = white_desat,
    tertiary = grey_600,
    secondary = amer_blue,  // TODO desat
    onSecondary = grey_500,
    surface = black_pure,
    onSurface = white_desat,
    surfaceVariant = grey_800,
    onSurfaceVariant = white_desat,
    error = orange_700,
    onError = orange_700
)

private val LightColorPalette = lightColorScheme(
    primary = amer_red,
    onPrimary = white_pure,
    tertiary = amer_blue_50,
    secondary = amer_blue,
    onSecondary = grey_500,
    surface = white_pure,
    onSurface = black_pure,
    surfaceVariant = white_pure,
    onSurfaceVariant = black_pure,
    error = orange_500,
    onError = orange_500,
    errorContainer = orange_500,
    onErrorContainer = orange_500

    /* Other default colors to override
    background = Color.White,
    onBackground = Color.Black,
    */
)

@Composable
fun ComposeTemplateTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colorScheme = colors,
        typography = DemoTypography,
        content = content
    )
}

Finally to make this example complete, here is how I call the Composable in the first place:

NameTextInput(name = personalInfo.name, onNameInfoValid = { isNameValidState.value = it })

...and the versions I am using

        appCompatVersion = '1.3.1'
        activityComposeVersion = '1.4.0'        
        composeVersion = '1.0.4'
        composeNavigationVersion = '2.4.0-alpha06'        
        kotlinVersion = '1.5.31'
        ktlint_version = "0.42.1"
        ktxVersion = '1.7.0'
        lifecycleVersion = '2.3.0'
        materialVersion = '1.5.0-alpha05'

Upvotes: 2

Views: 7613

Answers (1)

Abhimanyu
Abhimanyu

Reputation: 14787

This seems crazy. But this is what happens.

There seems to be some style conflict between Material 2 and Material 3 . (apparently obvious I guess).

Using

import androidx.compose.material3.Text

Material 3

But when using

import androidx.compose.material.Text

Material 2

This one line change solved the issue.

My complete material imports if needed.

import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Clear
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme

Additional helpful info,

I am maintaining complete 2 sets of resources.
This might help for components that are not supported in M3 yet.
Use appropriate imports in the composable.
We can use complete imports if a particular composable has both M2 and M3 components.

private val Material2DarkColorPalette = darkColors(
    primary = Primary,
    ...
)

private val Material2LightColorPalette = lightColors(
    primary = Primary,
    ...
)

private val LightColorPalette = lightColorScheme(
    primary = Primary,
    ...
)

private val DarkColorPalette = darkColorScheme(
    primary = Primary,
    ...
)

@Composable
fun Material2AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) {
    val colors = if (darkTheme) {
        Material2DarkColorPalette
    } else {
        Material2LightColorPalette
    }

    androidx.compose.material.MaterialTheme(
        colors = colors,
        typography = Material2Typography,
        shapes = Shapes,
        content = content
    )
}

@Composable
fun Material3AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colorScheme = colors,
        typography = Typography,
        content = content
    )
}

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) {
    Material2AppTheme(
        darkTheme = darkTheme,
        content = content,
    )
}

Upvotes: 4

Related Questions