How to reference theme attributes in Jetpack Compose? - android

So in the current andriod development, if we need to reference to a color set in the theme, we could simply do: (in layout xml)
....
<TextView
...
android:textColor="?attr/colorPrimary"
.../>
....
If I set a color blue in the theme. I will get that color in the textview above.
I was wondering how I could do the same in Jetpack Compose. In Compose, we do,
MaterialTheme(...) {
Column {
Text(
...
textStyle = TextStyle(color = ????) // How to I reference to the theme?
)
}
}

You can use something like:
Text(text = "....",
style = TextStyle(color = MaterialTheme.colors.primary))

Compose provide ColorPalette based on Material color specification to generate you app theme. It provide lightColorPalette and darkColorPalette for defining theme for light and dark mode respectively.
val lightThemeColors = lightColorPalette(
primary = Color(0xFFFFFFFF),
primaryVariant = Color(0xFFFFFFFF),
onPrimary = Color.Black,
secondary = Color.Transparent,
onSecondary = Color.White,
background = Color.White,
onBackground = Color(0xFF212121),
surface = Color.White,
onSurface = Color(0xFF212121),
error = Color.Red,
onError = Color.White
)
val darkThemeColors = darkColorPalette(
primary = Color(0xFF000000),
primaryVariant = Color(0xFF000000),
onPrimary = Color.White,
secondary = Color.White,
onSecondary = Color.White,
surface = Color(0xFF212121),
background = Color(0xFF212121),
onBackground = Color.White,
onSurface = Color.White,
error = Color.Red,
onError = Color.White
)
MaterialTheme(
colors = if(isSystemInDarkTheme()) darkThemeColors else lightThemeColors
) {
Column {
Text(
textStyle = TextStyle(color = MaterialTheme.colors.primary)
)
}
}
If you want to add more custom color to you theme you can use ColorPalette with extension variable like shown below
#Composable
val ColorPalette.myColor: Color
get() = if(!isLight) Color(0xFF424242) else Color.White
Now you can use MaterialTheme.colors.myColor to include.
If you want to include color from android colors xml you can do that using colorResource(id = R.color.anyName) function.

You can try this:
inline fun <T> Resources.Theme.getAttribute(
#AttrRes attr: Int,
block: (TypedArray) -> T,
): T {
val a = obtainStyledAttributes(intArrayOf(attr))
return block(a).also { a.recycle() }
}
fun Resources.Theme.getDrawableAttribute(#AttrRes attr: Int): Drawable =
getAttribute(attr) { it.getDrawable(0)!! }
fun Resources.Theme.getDimensionAttribute(#AttrRes attr: Int, defaultValue: Float = 0f): Float =
getAttribute(attr) { it.getDimension(0, defaultValue) }
fun Resources.Theme.getStringAttribute(#AttrRes attr: Int): String? =
getAttribute(attr) { it.getString(0) }
fun Resources.Theme.getColorAttribute(#AttrRes attr: Int, defaultValue: Int = 0): Int =
getAttribute(attr) { it.getColor(0, defaultValue) }
#Composable
fun getDimensionAttribute(#AttrRes attr: Int, defaultValue: Float = 0f): Float =
LocalContext.current.theme.getDimensionAttribute(attr, defaultValue)
#Composable
fun getStringAttribute(#AttrRes attr: Int): String? =
LocalContext.current.theme.getStringAttribute(attr)
#Composable
fun getColorAttribute(#AttrRes attr: Int, defaultValue: Int = 0): Int =
LocalContext.current.theme.getColorAttribute(attr, defaultValue)

For getting color from attributes I use this composable function:
#Composable
fun getColor(color: Int): Color {
return colorResource(LocalContext.current.getColorFromAttrs(color).resourceId)
}
fun Context.getColorFromAttrs(attr: Int): TypedValue {
return TypedValue().apply {
theme.resolveAttribute(attr, this, true)
}
}
Usages:
val color: Color = getColor(R.attr.colorText)

Related

Equivalent of "expandedHintEnabled" in Jetpack Compose TextField

Is there any way to put OutlinedTextField's label on top (expanded) and still have the placeholder, when OutlinedTextField is not in focus?
In xml layouts we have TextInputLayout that has expandedHintEnabled attribute that does the expanding.
Currently the placeholder applies an alpha modifier with this condition InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f and there isn't a parameter to achieve the same behaviour of expandedHintEnabled.
A workaround can be to use a visualTransformation to display a placeholder when the text is empty, removing the placeholder parameter.
Something like:
val textColor = if (text.isEmpty())
MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
else
LocalContentColor.current.copy(LocalContentAlpha.current)
val textStyle = if (text.isEmpty())
LocalTextStyle.current.merge(MaterialTheme.typography.subtitle1)
else
LocalTextStyle.current
TextField(
value = text,
onValueChange = { text = it },
//placeholder = { Text("Placeholder") },
label = { Text("Label") },
visualTransformation = if (text.isEmpty())
PlaceholderTransformation("Placeholder")
else VisualTransformation.None,
textStyle = textStyle,
colors = TextFieldDefaults.textFieldColors(
textColor = textColor
)
)
with:
class PlaceholderTransformation(val placeholder: String) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return PlaceholderFilter(text, placeholder)
}
}
fun PlaceholderFilter(text: AnnotatedString, placeholder: String): TransformedText {
var out = placeholder
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return 0
}
override fun transformedToOriginal(offset: Int): Int {
return 0
}
}
return TransformedText(AnnotatedString(placeholder), numberOffsetTranslator)
}

Reverse order of items in a Row

I have a composable component that represent a message.
Each message can either be incoming or outgoing, depending on that I would like to reverse all items in my message component.
The only way I found is to force a RTL layout, however it leads to text being reversed too.
Is there any other way around this?
MessageView.kt
#Composable
fun MessageView(
message: Message
) = Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.Bottom
) {
val (isIncoming) = message
val direction = if (isIncoming) {
LayoutDirection.Ltr
} else {
LayoutDirection.Rtl
}
CompositionLocalProvider(
LocalLayoutDirection provides direction
) {
MessageViewContent(message)
}
}
#Composable
private fun MessageViewContent(
message: Message
) = Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.Bottom
) {
val (isIncoming, text, at) = message
val background: Color
val textColor: Color
val timeColor: Color
val alignment: Alignment.Horizontal
val textAlignment: TextAlign
if (isIncoming) {
background = Color(0xFFEFEFEF)
textColor = Color(0xFF000000)
timeColor = Color(0xFF929292)
alignment = Alignment.End
textAlignment = TextAlign.Start
} else {
background = Color(0xFFE0727F)
textColor = Color(0xFFFEFEFE)
timeColor = Color(0xB3FEFEFE)
alignment = Alignment.Start
textAlignment = TextAlign.End
}
Image(
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
painter = painterResource(R.drawable.ic_launcher_background),
contentDescription = null
)
Spacer(modifier = Modifier.width(12.dp))
Column(
modifier = Modifier
.weight(1F, fill = false)
.wrapContentWidth()
.background(
color = background,
shape = RoundedCornerShape(8.dp)
)
.padding(4.dp),
horizontalAlignment = alignment
) {
Text(
modifier = Modifier.padding(6.dp),
style = TextStyle(
fontSize = 16.sp,
color = textColor
),
text = text
)
Text(
style = TextStyle(
fontSize = 10.sp,
color = timeColor,
textAlign = textAlignment
),
text = "${at.hour}:${at.minute}",
)
}
Spacer(modifier = Modifier.width(60.dp))
}
So, I figured out a solution
One can create a custom Horizontal Arrangement which uses code from already existing End arrangement.
HorizontalArrangementExts.kt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
val Arrangement.Reverse: Arrangement.Horizontal
get() = ReverseArrangement
object ReverseArrangement : Arrangement.Horizontal {
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
layoutDirection: LayoutDirection,
outPositions: IntArray
) = if (layoutDirection == LayoutDirection.Ltr) {
placeRightOrBottom(totalSize, sizes, outPositions, reverseInput = true)
} else {
placeLeftOrTop(sizes, outPositions, reverseInput = false)
}
// Had to copy function from sources because it is marked internal
private fun placeRightOrBottom(
totalSize: Int,
size: IntArray,
outPosition: IntArray,
reverseInput: Boolean
) {
val consumedSize = size.fold(0) { a, b -> a + b }
var current = totalSize - consumedSize
size.forEachIndexed(reverseInput) { index, it ->
outPosition[index] = current
current += it
}
}
// Had to copy function from sources because it is marked internal
private fun placeLeftOrTop(size: IntArray, outPosition: IntArray, reverseInput: Boolean) {
var current = 0
size.forEachIndexed(reverseInput) { index, it ->
outPosition[index] = current
current += it
}
}
// Had to copy function from sources because it is marked private
private inline fun IntArray.forEachIndexed(reversed: Boolean, action: (Int, Int) -> Unit) {
if (!reversed) {
forEachIndexed(action)
} else {
for (i in (size - 1) downTo 0) {
action(i, get(i))
}
}
}
}

Compose - Custom MaterialTheme colors not working?

I want to custom colors system using Compose, but it isn't working. It effected by colors in themes.xml.
Activity
class DemoComposeMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val colorPrimary = colorResource(R.color.md_green_500)
val colorSecondary = colorResource(R.color.md_orange_500)
val colors = lightColors(
primary = colorPrimary,
primaryVariant = colorPrimary,
onPrimary = Color.White,
secondary = colorSecondary,
secondaryVariant = colorSecondary,
onSecondary = Color.White)
MaterialTheme(colors = colors) {
// TODO
}
}
}
}
Please help me. Thanks.
The statusbar color is based on the android:statusBarColor defined in your app theme.
If you want to change the statusBar color you can use the accompanist library.
Something like.
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
SideEffect {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = useDarkIcons
)
// setStatusBarsColor() and setNavigationBarsColor() also exist
}

IllegalStateException: CompositionLocal LocalConfiguration not present Android

I'm trying to use staticCompositionLocalOf in Jetpack Compose according to this article on Medium.
This is my ProvidableCompositionLocal
val lightColors = lightColors(
primary = LightColor.BackgroundWhite,
primaryVariant = LightColor.ToolbarWhite,
secondary = LightColor.FabBlue,
secondaryVariant = LightColor.BackgroundWhite,
surface = LightColor.SurfaceWhite,
onPrimary = LightColor.TextBlack,
onSecondary = LightColor.TextBlack,
onSurface = LightColor.TextBlack
)
val darkColors = darkColors(
primary = DarkColor.BackgroundBlue,
primaryVariant = DarkColor.ToolbarBlue,
secondary = DarkColor.FabGrey,
secondaryVariant = DarkColor.BackgroundBlue,
surface = DarkColor.SurfaceBlue,
onPrimary = Color.White,
onSecondary = Color.White,
onSurface = Color.White
)
private val DarkColorPalette =
Colors(
material = darkColors,
toolbar = DarkColor.ToolbarBlue,
background = DarkColor.BackgroundBlue,
surface = DarkColor.SurfaceBlue,
fab = DarkColor.FabGrey,
pink = DarkColor.Pink
)
private val LightColorPalette =
Colors(
material = lightColors,
toolbar = LightColor.ToolbarWhite,
background = LightColor.BackgroundWhite,
surface = LightColor.SurfaceWhite,
fab = LightColor.FabBlue,
pink = LightColor.Pink
)
val TheColor: Colors
#Composable
#ReadOnlyComposable
get() = LocalColors.current
}
val LocalColors = staticCompositionLocalOf { DarkColorPalette }
This is the wrapper around the normal Colors class provided by Compose class. The material variable is the normal Colors class.
data class Colors(
val material: Colors,
val toolbar: Color,
val background: Color,
val surface: Color,
val fab: Color,
val pink: Color
) {
val primary: Color get() = material.primary
val primaryVariant: Color get() = material.primaryVariant
val secondary: Color get() = material.secondary
val secondaryVariant: Color get() = material.secondaryVariant
// val background: Color get() = material.background
// val surface: Color get() = material.surface
val error: Color get() = material.error
val onPrimary: Color get() = material.onPrimary
val onSecondary: Color get() = material.onSecondary
val onBackground: Color get() = material.onBackground
val onSurface: Color get() = material.onSurface
val onError: Color get() = material.onError
val isLight: Boolean get() = material.isLight
}
I have also provided it in my Theme function as shown below. I'm getting the darkTheme from Android DataStore
#Composable
fun BMICalculatorTheme(
darkTheme: Boolean,
content: #Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
CompositionLocalProvider(LocalColors provides colors) {
MaterialTheme(
colors = colors.material,
typography = CabinTypography,
shapes = Shapes,
content = content
)
}
}
However, I'm getting the below error but can't find any online resources that can help me fix it. In case any further information is needed, I would be more than happy to clarify. Any help would be highly appreciated.
IllegalStateException: CompositionLocal LocalConfiguration not present
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.noLocalProvidedFor(AndroidCompositionLocals.android.kt:123)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.access$noLocalProvidedFor(AndroidCompositionLocals.android.kt:1)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$LocalConfiguration$1.invoke(AndroidCompositionLocals.android.kt:44)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$LocalConfiguration$1.invoke(AndroidCompositionLocals.android.kt:43)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at androidx.compose.runtime.LazyValueHolder.getCurrent(ValueHolders.kt:29)
at androidx.compose.runtime.LazyValueHolder.getValue(ValueHolders.kt:31)
at androidx.compose.runtime.ComposerImpl.resolveCompositionLocal(Composer.kt:1895)
at androidx.compose.runtime.ComposerImpl.consume(Composer.kt:1865)
at androidx.compose.foundation.DarkTheme_androidKt.isSystemInDarkTheme(DarkTheme.android.kt:52)
at com.octagon_technologies.bmicalculator.data.ThemeDataStore$isDarkMode$$inlined$map$1$2.emit(Collect.kt:135)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catchImpl$$inlined$collect$1.emit(Collect.kt:134)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:77)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:59)
at androidx.datastore.core.SingleProcessDataStore$data$1$invokeSuspend$$inlined$map$1$2.emit(Collect.kt:139)
at kotlinx.coroutines.flow.FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1$lambda$1.emit(Collect.kt:137)
at kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:348)
at kotlinx.coroutines.flow.StateFlowImpl$collect$1.invokeSuspend(Unknown Source:12)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
The problem appears to be here:
... ThemeDataStore$isDarkMode$$inlined$map$1$2.emit(Collect.kt:135)
From the stack trace it looks like the isSystemInDarkTheme is being called inside a lambda passed to collect() which, since it is an #Composable function, it should only be called from another #Composable function and cannot be called inside a flow collect().
The compiler should have reported this an error. Please consider reporting this as a compose compiler plugin bug.

How do I use Color resource directly in Jetpack Compose?

I want to use custom colors defined in the colors.xml class directly without using the Material theme colors or the default theme provided by the Jetpack. Is there any straightforward way to do it?
You can use colorResource() which loads a color resource.
Text(
text = "Hello World",
color = colorResource(R.color.purple_200)
)
To use color in jetpack compose using recommended create a package ui.theme in com.<domain_name>.<app_name> which will likely be present by default if you are creating empty compose project. Now create Color.kt and Theme.kt kotlin files if they are not present in your project.
In Color.kt add the colors you need
package com.<domain_name>.<app_name>.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Flower = Color(0xFF4CAF50)
val Deer = Color(0xFFFF5722)
val Mango = Color(0xFFFF9800)
val AppbarColor = Color(0xFF2196F3)
Here is ready to use a Material Color template made by me
There are 3 common ways of using colors
Method 1 : Directly use color
import com.<domain_name>.<app_name>.ui.theme.*
Text(text = "Hello ", color = Flower)
Method 2 : Override default MaterialTheme colors
Now in, Theme.kt
private val DarkColorPalette = darkColors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200,
onBackground = Flower //Custom color
)
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200,
onBackground = Deer //Custom color
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
#Composable
fun NotepadTheme(darkTheme: Boolean = isSystemInDarkTheme(),
content:#Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ColorApp()
}
}
}
#Composable
fun ColorApp() {
ColorTheme {
Surface(modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
#Composable
fun Greeting(name: String) {
Text(text = "Hello $name!", color = MaterialTheme.colors.onBackground) //Using color
}
#Preview(
showBackground = true, name = "Light mode",
uiMode = Configuration.UI_MODE_NIGHT_NO or
Configuration.UI_MODE_TYPE_NORMAL
)
#Preview(
showBackground = true, name = "Night mode",
uiMode = Configuration.UI_MODE_NIGHT_YES or
Configuration.UI_MODE_TYPE_NORMAL
)
#Composable
fun DefaultPreview() {
ColorApp()
}
Method 3 : Custom theme (Recommended method)
Text(text = "Hello ", color = AppNameTheme.colors.customColor)

Categories

Resources