Currently, I am migrating one of my apps to Material Design 3 which is entirely written in Kotlin using Jetpack Compose.
While using Material Design 2, I was able to change the emphasis of the text using the code below.
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = "Hello, world",
style = MaterialTheme.typography.h6,
fontWeight = FontWeight.SemiBold,
)
}
However, the same code doesn't work for Material Design 3 and the text has the default emphasis. Also, I can't find the relevant function anywhere for Material Design 3. I would like to know if there is any official way to achieve the same effect.
MaterialTheme.typography.h6 is from Material 2, which means you're using Text composable from Material 2 too.
Material 3 analog of h6 is MaterialTheme.typography.headlineSmall.
Make sure you have correct imports of both Text and MaterialTheme - these should be imported from androidx.compose.material3 package. Also make sure you provide a correctly imported theme, e.g. here.
Wrong imports is the most common mistake when migrating to M3, so be patient with it.
Also note that LocalContentAlpha doesn't exists in M3, providing M2 version will have no effect on M3 views. You can compare how Text composable determines its color in M2 and in M3.
I'm not sure wether it's gonna be added later (it's alpha after all), or it's handled in some other way in M3, here's a workaround(which is not perfect for sure):
CompositionLocalProvider(LocalContentColor provides LocalContentColor.current.copy(alpha = 0.4f)) {
p.s. LocalContentColor needs to be imported from M3 too
The "Emphasis and content alpha" section in the Migrate from Material 2 to Material 3 in Compose details the API changes.
Material2:
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LocalContentAlpha
// High emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(…)
}
Material3:
import androidx.compose.material3.LocalContentColor
// High emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
Icon(…)
}
Related
I'm sure this is a newbie question, but I'm trying to implement a Material 3 theme. I have added the themes.kt & colors.kt to my com.my.project.ui.theme package. But I don't understand what I can do next to implement these colors as my app is not using these colors and is still using the default material 3 colors that came when I started the app. I'm not finding the disconnect.
I have added the dependencies and removed the old Material dependency
//implementation 'androidx.compose.material:material:1.3.1'
implementation 'androidx.compose.material3:material3:1.1.0-alpha03'
implementation "androidx.compose.material3:material3-window-size-class:1.1.0-alpha03"
implementation "com.google.accompanist:accompanist-flowlayout:0.24.8-beta"
One thing I notice is the function in themes.kt is saying it is never used (grayed out).
#Composable
fun DzoicTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: #Composable () -> Unit) {
val useDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colors = when {
useDynamicColors && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
useDynamicColors && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
darkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
Where does this function get called (DzoicTheme)? Default was AppTheme but instructions said to change it. Is the name important here? Most tutorials show utilizing material 3 colors from the activity compose functions but I do all my design in xml layout files, does this matter? Any help here would be appreciated!
I am learning Jetpack compose of Android Development.
Sometimes, I use
MaterialTheme.colors
MaterialTheme.coloScheme
because one of them shows red.
For example,
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
So, what's the difference and which one is better or how to use them properly?
These are 2 different color selection systems. In material design 2 Color swatches with color between 100, and 900 is used. You can check out swatches in material color picker. M3 uses shades between 0-100 from their new color system HCT.
TL;DR
When you pick Composables suc as Button from Material Design2 colors from M2 are used. When you pick Composables from Material Design3 tokens from M3 are used.
Material Design2
When you select primary 500 and 700, for selecting secondary 200 and 700 variants are used.
When you call androidx.compose.material.MaterialTheme.colors.x
you are getting these colors.
Material Design3
For Material Design 3 they invented a new color system(RGB, HSV, HSL) called HCT(hue-colorfulness-tone). If you are interested in details you can check out this blog. Now, instead of colors with 200 and 900 colors are selected as tones between 0 and 100.
There is util library from google that creates these tones from the color you picked. But there was a bug creating colors last time i checked.
I also built a M2, and M3 color selection library that depends google's library for M3 creation.
Google's theme builder to create M3 colors for Compose
When you pick primary, secondary, teriatry in material builder or any tool, or using default colors by creating M3 project variants of 40-20, etc are created for primary, secondary color roles. You might pick Red but its tone(40) is used for Primary color.
#FF00000 -> #c001000
Color Roles
The primary key color is used to derive roles for key components
across the UI, such as the FAB, prominent buttons, active states, as
well as the tint of elevated surfaces.
The secondary key color is used for less prominent components in the
UI such as filter chips, while expanding the opportunity for color
expression.
The tertiary key color is used to derive the roles of contrasting
accents that can be used to balance primary and secondary colors or
bring heightened attention to an element. The tertiary color role is
left for teams to use at their discretion and is intended to support
broader color expression in products.
You can check out official m3 page when to use primary, secondary and teriatry colors
Primary
Primary roles are used for key components across the UI, such as the FAB, prominent buttons, active states, as well as the tint of elevated surfaces.
Secondary
Secondary roles are used for less prominent components in the UI, such as filter chips, while expanding the opportunity for color expression.
Tertiary
Tertiary roles are used for contrasting accents that can be used to balance primary and secondary colors or bring heightened attention to an element, such as an input field.
The tertiary color role is left for makers to use at their discretion and is intended to support broader color expression in products.
How are these in code are like this? As in mentioned above Composables pick respective color tokens, Buttons primary to match with your theme's set colors.
internal object FilledButtonTokens {
val ContainerColor = ColorSchemeKeyTokens.Primary
val ContainerElevation = ElevationTokens.Level0
val ContainerHeight = 40.0.dp
val ContainerShape = ShapeKeyTokens.CornerFull
val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
val DisabledContainerElevation = ElevationTokens.Level0
const val DisabledContainerOpacity = 0.12f
val DisabledLabelTextColor = ColorSchemeKeyTokens.OnSurface
const val DisabledLabelTextOpacity = 0.38f
val FocusContainerElevation = ElevationTokens.Level0
val FocusLabelTextColor = ColorSchemeKeyTokens.OnPrimary
val HoverContainerElevation = ElevationTokens.Level1
val HoverLabelTextColor = ColorSchemeKeyTokens.OnPrimary
val LabelTextColor = ColorSchemeKeyTokens.OnPrimary
val LabelTextFont = TypographyKeyTokens.LabelLarge
val PressedContainerElevation = ElevationTokens.Level0
val PressedLabelTextColor = ColorSchemeKeyTokens.OnPrimary
val DisabledIconColor = ColorSchemeKeyTokens.OnSurface
const val DisabledIconOpacity = 0.38f
val FocusIconColor = ColorSchemeKeyTokens.OnPrimary
val HoverIconColor = ColorSchemeKeyTokens.OnPrimary
val IconColor = ColorSchemeKeyTokens.OnPrimary
val IconSize = 18.0.dp
val PressedIconColor = ColorSchemeKeyTokens.OnPrimary
}
In Material 2 you would use one and in Material 3 the other. For instance:
Material 2:
Surface(
color = MaterialTheme.colors.surface,
contentColor = contentColorFor(color),
// ...
TopAppBar(
backgroundColor = MaterialTheme.colors.primarySurface,
contentColor = contentColorFor(backgroundColor),
// ...
Material 3:
Card(
colors = CardDefaults.cardColors(
containerColor =
if (isSelected) MaterialTheme.colorScheme.primaryContainer
else
MaterialTheme.colorScheme.surfaceVariant)
) {
Text(
text = “Dinner club”,
style = MaterialTheme.typography.bodyLarge,
color =
if (isSelected) MaterialTheme.colorScheme.onPrimaryContainer
else MaterialTheme.colorScheme.onSurface, ),
….
….
}
They are the same.
colors is available only on material 2 so when you use MaterialTheme.colors you will notice that MaterialTheme is imported from material with import androidx.compose.material.MaterialTheme
colorScheme is available only on material 3 so when you use MaterialTheme.colorScheme you will notice that MaterialTheme is imported from material3 with import androidx.compose.material3.MaterialTheme
So if you are using material 3 in your project make sure that you are working with colorScheme and if you are using material 2 make sure that you are using color.
Basically ,
MaterialTheme.colorScheme.primary is part of Material 3 Design library.
MaterialTheme.colors.background is part of Legacy Material 2 Design Library.
Material 3 library for compose has additional features compared to Material 2.
I have a CustomTextView, I used to add style to it using xml style attribute, but with compose I need to understand how to do it.
When using this :
AndroidView( factory = { context->
RegularAmountTextView(ContextThemeWrapper(context,R.style.TextAppearance_MyTheme_Heading_Small ),null ).apply {
text ="1234"
}
}, modifier = Modifier.fillMaxWidth().wrapContentHeight() )
I get error: Infinite cycle trying to resolve '?attr/textColorHighlight': Render may not be accurate. in preview panel.
while on running on device I get:
java.lang.UnsupportedOperationException: Failed to resolve attribute at index 4: TypedValue{t=0x2/d=0x1010099 a=1}
at android.content.res.TypedArray.getColor(TypedArray.java:529)
at android.widget.TextView.readTextAppearance(TextView.java:4345)
at android.widget.TextView.<init>(TextView.java:1340)
at android.widget.TextView.<init>(TextView.java:1258)
at androidx.appcompat.widget.AppCompatTextView.<init>(AppCompatTextView.java:108)
at androidx.appcompat.widget.AppCompatTextView.<init>(AppCompatTextView.java:103)
Similar error only. If I remove the style then it works. Has Android team provided any solution for this.
Another question: On facing these errors I was thinking is using compose on production a wise decision, or we should wait till android team provides proper inter-portability.
I know how to implement BottomSheet in Material 2 Jetpack Compose using BottomSheetScaffold.
But there is no BottomSheetScaffold in Material 3. Also, there is nothing in official samples about BottomSheet.
So I was able to make it work!
It seems that, as of today, BottomSheetScaffold is not available yet on Material3, this is discussed in this issue I found digging around: https://issuetracker.google.com/issues/229839039
I quote the important part from the reply of a google dev:
we aren't in a super easy spot with Swipeable. It currently has a number of critical bugs that need to be addressed first (we are working on this), which is why we are limiting the surface we are exposing for Swipeable in M3 for the time. Our plan for the coming months is to focus on this specific area and improve developer experience.
Material 3 for Jetpack Compose is still in alpha - this means we
consider components production-ready, but the API shape is flexible
while in alpha. This gives us space to iterate while getting
real-world feedback from developers, which ultimately helps improve
your experience. Copy-pasting source code for components that are not
(fully) implemented or exposed in an alpha version can be a good thing
to do in the meantime! Owning the source code while the API shape is
still flexible gives you a number of benefits like ease of updating
dependencies, even if the APIs change, and allows you to evolve your
components in your own pace.
So I just followed the advice and I copy pasted BottomSheetScaffold into my project. Of course it did not work straight away because of a few missing classes and some minor API changes. At the end I was able to make it work by pulling and hacking the following classes and adding them to my project:
BottomSheetScaffold.kt
Drawer.kt
Strings.kt
Swipeable.kt
I have created a gist with the source code if you want to try:
https://gist.github.com/Marlinski/0b043968c2f574d70ee6060aeda54882
You will have to change the import to make it work on your project as well as add the "-Xjvm-default=all" option by adding the following into your gradle file in the android{} section:
android{
...
kotlinOptions {
freeCompilerArgs += ["-Xjvm-default=all"]
// "-Xjvm-default=all" option added because of this error:
// ... Inheritance from an interface with '#JvmDefault' members is only allowed with -Xjvm-default option
// triggered by porting BottomSheetScaffold for Material3 on Swipeable.kt:844
}
}
It works very well for me, will keep this solution until it is officially supported in material3.
Hope it helps!
I got pretty similar results using a fullscreen dialog with AnimatedVisibility, here is the code if interested:
// Visibility state for the dialog which will trigger it only once when called
val transitionState = remember {
MutableTransitionState(false).apply {
targetState = true
}
}
Dialog(
onDismissRequest = {} // You can set a visibility state variable to false in here which will close the dialog when clicked outside its bounds, no reason to when full screen though,
properties = DialogProperties(
// This property makes the dialog full width of the screen
usePlatformDefaultWidth = false
)
) {
// Visibility animation, more information in android docs if needed
AnimatedVisibility(
visibleState = transitionState,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = ...
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = ...
)
)
) {
Box(
modifier = Modifier.fillMaxSize().background(color = ...)
) {
// Your layout
// This can be any user interraction that closes the dialog
Button(
transitionState.apply { targetState = false }
) ...
}
}
All of this is in a composable function that gets called when a UI action to open said dialog is performed, it's not ideal but it works.
Hope I was able to help!
There is already a great answer by Marlinski, but i would like to add that there is also a ModalBottomSheetLayout which also does not have any implementation for Material 3.
I created a gist for people who need it in use right now:
https://gist.github.com/Pasha831/bdedcfee01acdc96cf3ae643da64f88a
We finally have ModalBottomSheet in Material3.
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
val bottomSheetState = rememberSheetState(skipHalfExpanded = true)
// Sheet content
if (openBottomSheet) {
ModalBottomSheet(
onDismissRequest = { openBottomSheet = false },
sheetState = bottomSheetState,
) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
// Note: If you provide logic outside of onDismissRequest to remove the sheet,
// you must additionally handle intended state cleanup, if any.
onClick = {
scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
if (!bottomSheetState.isVisible) {
openBottomSheet = false
}
}
}
) {
Text("Hide Bottom Sheet")
}
}
}
}
For more link here: https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ModalBottomSheet(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.SheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,kotlin.Function0,kotlin.Function1)
I'm trying to test a Text that on my component I can print it in different colors, so on my test I'm verifying it gets the expected color. I was looking for a method to return the color but I did not find any.
From now I'm asserting that the text is correct and the visibility is correct, but when trying to find the method to get the colour I get too deep and I'm looking for a simpler solution.
composeTestRule.onNode(hasTestTag("testTagForButton"), true)
.assertExists()
.assertTextEquals("Testing")
I've check that I can do something like .fetchSemanticsNode().layoutInfo.getModifierInfo() to get into the Modifier and perhaps from there I can get the colour, but it's too much maybe. Also I've found this .captureToImage() that perhaps I could get the colour on it, but since I had to put the pixels I decided that it's not the way.
Is there any simple way to get that?
I am by no means a compose expert, but just looking at compose source code, you could utilize their GetTextLayoutResult accessibility semantic action. This will contain all the properties that are used to render the Text on a canvas.
Some quick and dirty extension functions I put up for convenience:
fun SemanticsNodeInteraction.assertTextColor(
color: Color
): SemanticsNodeInteraction = assert(isOfColor(color))
private fun isOfColor(color: Color): SemanticsMatcher = SemanticsMatcher(
"${SemanticsProperties.Text.name} is of color '$color'"
) {
val textLayoutResults = mutableListOf<TextLayoutResult>()
it.config.getOrNull(SemanticsActions.GetTextLayoutResult)
?.action
?.invoke(textLayoutResults)
return#SemanticsMatcher if (textLayoutResults.isEmpty()) {
false
} else {
textLayoutResults.first().layoutInput.style.color == color
}
}
Which can be then used like this:
composeTestRule.onNode(hasTestTag("testTagForButton"), true)
.assertExists()
.assertTextEquals("Testing")
.assertTextColor(Color.Black)
I am unable to comment post above, also didn't find question about checking background color, so decide to place my version here.
private fun hasBackground(node: SemanticsNode, color: Color, shape: Shape): Boolean {
return node.layoutInfo.getModifierInfo().filter { modifierInfo ->
modifierInfo.modifier == Modifier.background(color, shape)
}.size == 1
}
To test background color and don't touch debug inspection info (this isn't for testing) we are unable to test only background color, but can test whole background by comparing production background (which placed into modifier) with our testing one.
Hope it help somebody.