I am trying to migrate an app from XML layouts to jetpack compose. As the app allows setting different user themes, I wanted to use the XML themes until all files are migrated. However, I just can't get it to work.
Basically I am doing as described in
https://material-components.github.io/material-components-android-compose-theme-adapter/#compose-material-3
I have a simple activity with a defined Composable:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import at.example.myapp.ui.composables.about.About
import com.google.android.material.composethemeadapter3.Mdc3Theme
class AboutActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(MainActivity.getUserTheme(this))
super.onCreate(savedInstanceState)
setContent {
// Use MdcTheme instead of MaterialTheme
// Colors, typography, and shape have been read from the
// View-based theme used in this Activity
Mdc3Theme {
About()
}
}
}
}
setTheme(...) correctly loads the theme, the color on the very top is corretly set, but nothing else. All other colors, styles and fonts are not set in the composable.
The About() composable itself has nothing special, just a few texts, images and a card.
I am importing the following versions:
//JETPACK COMPOSE
// Integration with activities
implementation 'androidx.activity:activity-compose:1.4.0'
// Compose Material Design
implementation 'androidx.compose.material:material:1.1.1'
// Animations
implementation 'androidx.compose.animation:animation:1.1.1'
// Tooling support (Previews, etc.)
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
// UI Tests
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
// Theme compatibility with XML
implementation "com.google.android.material:compose-theme-adapter-3:1.0.6"
So the compose-theme-adapter-3 should actually do the job (if I understood the documentation correctly).
I played around with different combinations and parameters for Mdc3Theme { ... }, but without success, the colors won't just load.
In the resources I have the colors defined as expected by Material 3:
<resources>
<!-- Base application theme. -->
<style name="Theme.MyApp" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">#color/md_theme_light_primary</item>
<item name="colorOnPrimary">#color/md_theme_light_onPrimary</item>
<item name="colorPrimaryContainer">#color/md_theme_light_primaryContainer</item>
<item name="colorOnPrimaryContainer">#color/md_theme_light_onPrimaryContainer</item>
<item name="colorSecondary">#color/md_theme_light_secondary</item> ...
As I couldn't find other examples, I hope that it is correct to just basically load the Theme like this:
setContent {
// Use MdcTheme instead of MaterialTheme
// Colors, typography, and shape have been read from the
// View-based theme used in this Activity
Mdc3Theme {
About()
}
}
Playing around with compose-theme-adapter (for Material 2) also didn't help.
I'm really stuck here. Is there anyone who could help or give an example about how to get the Mdc3 Theme applied properly from XML?
EDIT:
For completion, here's also the About() Composable:
#Composable
fun About() {
val defaultPadding = 8.dp
val widePadding = 16.dp
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
ImageVector.vectorResource(id = R.drawable.ic_babyfootsteps),
null,
modifier = Modifier
.padding(widePadding)
.size(100.dp, 100.dp)
.background(Color(0xFFFF0077))
)
Text(
stringResource(id = R.string.app_name),
fontSize = 24.sp,
modifier = Modifier.padding(defaultPadding),
)
Text(stringResource(id = R.string.about_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE))
Text(stringResource(id = R.string.about_app_codename, BuildConfig.versionCodename))
Spacer(modifier = Modifier.padding(defaultPadding))
...
Related
During cetrain times I want to use my own color resource ColorGreen to change the background colour of my custom SmallTopAppBar, and during other times I don't want to use it so that the default black and white colours are used instead depending on the device's current theme. What is the best way to avoid a possible null pointer exception? Should an 'if null' statement be used in the custom toolbar code? Should null be used in the activity declaration where the underscores are?
Color.kt
val ColorGreen = Color(0,110,20,255)
Custom toolbar code
package com.mycompany.myapp.ui.components
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
#Composable
fun MySmallTopAppBar(
backgroundColor: Color,
title: String,
titleColor: Color
) {
SmallTopAppBar(
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = backgroundColor),
title = {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Start,
maxLines = 1,
color = titleColor
)
}
)
}
within MainActivity.kt
...
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Scaffold(
topBar = { MySmallTopAppBar(ColorGreen, getGreetingMessage(), __) }
) {
}
}
}
}
...
In such cases you need to inspect component source code to understand how it works.
In case of Text, default color value is Color.Unspecified - if this value is used, color will be taken from the style or from LocalContentColor (of style value is unspecified too). So if you want to follow default behaviour, you need to pass this value it too:
val backgroundColor: Color?
// ...
MySmallTopAppBar(backgroundColor, getGreetingMessage(), if (backgroundColor == null) Color.Unspecified else Color.SomeColor)
In case if smallTopAppBarColors default value is internal, plus since it may change in the future(as material 3 is in alpha so far), the most correct way would be something like this:
colors = if (backgroundColor != null)
TopAppBarDefaults.smallTopAppBarColors(containerColor = backgroundColor)
else
TopAppBarDefaults.smallTopAppBarColors(),
I wrote some Jetpack Compose Demo, but I found library bug about adapt dark mode, therefore I want to show light mode only in my App, however when I set <item name="android:forceDarkAllowed" tools:targetApi="q">false</item> and AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) , those do not work, any idea for show light mode only for Jetpack Compose?
The color we used for compose is not defined in xml, should be something like below:
#Composable
fun MyComposeTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
As you can see, you can pass any color as you wish in MaterialTheme function call, just remove the dark mode check will do the trick.
Simply add an item in both res/theme.xml and res/theme.xml(night) file is <item name="android:windowBackground">#color/white</item> in both darkand light mode it will be white.
Thank you.
on theme.kt
change this
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
to this
val colors = LightColorPalette
How do I reference a resource style from Compose widget?
styles.xml
<style name="Style.Monet.TextView.HeaderSubtext" parent="Widget.AppCompat.TextView">
<item name="android:textColor">#737373</item>
<item name="android:textSize">12sp</item>
<item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginBottom">16dp</item>
<item name="android:layout_marginEnd">24dp</item>
</style>
MyComponent.kt
Text(text = "June 2021", style = TextStyle(R.style.Style_TextView_HeaderSubtext))
But this doesn't work. Is there a way to make this work?
You can't do that, because Compose Text is styled differently, and TextStyle it not responsible so all xml style is responsible. As an example, you cannot add margins.
You can create compose TextStyle:
val textStyle = TextStyle(
color = Color(0xFF737373),
fontSize = 12.sp,
)
And use it globally in your project or pass to your theme. This is a preferred way to use styles in compose, check out more about it in the theming documentation. Customize one of material available styles:
val typography = Typography(
body1 = TextStyle(
color = Color(0xFF737373),
fontSize = 12.sp,
)
)
Pass it to your theme:
#Composable
fun ComposePlaygroundTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkThemeColors
} else {
LightThemeColors
}
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content,
)
}
Apply Theme at the composable root:
setContent {
ComposePlaygroundTheme {
// your composables
}
}
After that you can use it like this:
Text("",
style = MaterialTheme.typography.body1,
)
To apply margins in compose you need to use padding modifier. Check out more about layout in compose in the layout documentation:
If you wanna reuse same styled text in compose, you can create your own composable with predefined style and padding:
#Composable
fun ProjectText(text: String, modifier: Modifier) {
// without material theme you can just define text style here and pass to text
// val textStyle = TextStyle(
// color = Color(0xFF737373),
// fontSize = 12.sp,
)
Text("",
style = MaterialTheme.typography.body1,
modifier = modifier
.padding(start = 16.dp, end = 24.dp, bottom = 16.dp)
)
}
Usage:
ProjectText("June 2021")
In Jetpack Compose, TopAppBar shows default background color irrespective of what we added to themes.xml.
So how can we change TopAppBar background color from themes.xml so it's applied globally to the App?
TopAppBar(
title = { Text("Activity") },
navigationIcon = {
IconButton(onClick = { onBackPressed() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = null)
}
}
)
themes.xml
<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">#android:color/black</item>
<item name="colorPrimaryVariant">#android:color/holo_orange_dark</item>
<item name="colorOnPrimary">#android:color/white</item>
</style>
Note : we can change it through backgroundColor attribute of TopAppBar but here I want to achieve it globally.
The accepted answer explains what to do adequately. There is one thing you might need to keep in mind, though.
TopAppBar uses MaterialTheme.colors.primarySurface as the background color, and it's defined as the following.
[androidx/compose/material/Colors.kt]
val Colors.primarySurface: Color get() = if (isLight) primary else surface
In other words, if the device is in the light mode, it uses primary, and in the dark mode, it uses surface.
Suppose I have a theme with everything as default values.
private val DarkColorPalette = darkColors()
private val LightColorPalette = lightColors()
#Composable
fun ComposeTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: #Composable () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
)
lightColors and darkColors have the following values by default.
fun lightColors(
primary: Color = Color(0xFF6200EE),
...
)
fun darkColors(
surface: Color = Color(0xFF121212),
...
)
When the device is in the light mode, primary (0xFF6200EE) will be TopAppBar's background color.
But when the device is in the dark mode, surface (0xFF121212) is not TopAppBar's background color. It's slightly lighter; 0xFF282828 to be exact.
The reason is TopAppBar has a built-in elevation, which is 4.dp by default.
[androidx/compose/material/AppBar.kt]
#Composable
fun TopAppBar(
...
elevation: Dp = AppBarDefaults.TopAppBarElevation // 4.dp
) {
This most likely won't cause a problem, but it might matter if you want to apply the exactly same background color to somewhere else, such as setting the same color for the status area's background.
val systemUiController = rememberSystemUiController()
systemUiController.setSystemBarsColor(color = Color(0xFF282828))
Note that for this particular case, it would be easier if you just go full screen and add padding at the top.
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.systemBarsPadding().fillMaxSize(), // <--
) {
...
}
}
}
For the TopAppBar and the other composables the basis of theming is the MaterialTheme composable and not the AppCompat/MaterialComponents XML themes.
The TopAppBar uses the backgroundColor attribute.
The default value is defined by MaterialTheme.colors.primarySurface.
You can change these colors globally defining your theme adding your Colors and passing them to a MaterialTheme:
private val LightColors = lightColors(
primary = Yellow500,
//...
)
Otherwise you can simply use :
TopAppBar(
title = { Text("Activity") },
backgroundColor = /*...*/,
/* ... */
)
If you want to use the AppCompat XML themes in Jetpack Compose you can use the AppCompat Compose Theme Adapter provided by the accompanist library.
When you create a new project in studio, you get a file named Theme.kt, in which there are color palettes named lightColors and darkColors. You should modify the parameters of those values to achieve the result globally in your app.
Does somebody know how to change default style to button?
Style in xml:
<item name="materialButtonStyle">#style/ButtonStyle</item>
And I want to convert it to Jetpack Compose.
In default compose sample(Android Studio Canary) You can see ui.theme folder and it's a analog of values folder but without Strings and Dimens. So how I can add Strings and Dimens to this compose folder?
As described in the nglauber answer you can customize the shape, typography and color in your theme, or in the Button parameters.
Also you can override these values and build a default button style.
Something like:
#Composable
fun DefaultButtonStyle(content: #Composable () -> Unit) {
MaterialTheme(
//override the shape
shapes = MaterialTheme.shapes.copy(small = CutCornerShape(12.dp)),
//Override the typography.button using the merge method
typography = MaterialTheme.typography.copy(
button = MaterialTheme.typography.button.merge(TextStyle(fontSize = 20.sp))),
//override the colors define in the material theme
colors = MaterialTheme.colors.copy(
primary = Color.Yellow,
onPrimary = Color.Blue)
) {
content()
}
}
Then just use it with:
DefaultButtonStyle() {
Button(onClick = { /*....*/ }) {
Text(text = "BUTTON")
}
}
If you look into the Button source, you'll notice that it uses a couple of default values that you can customize (via params or via custom style).
shape: Uses MaterialTheme.shapes.small (you can customized this field in your style);
val shapes = Shapes(
small = CutCornerShape(4.dp), // << here
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
colors: which is an instance of ButtonColors that provides backgroundColor, contentColor, disabledBackgroundColor and disabledContentColor. Look into the Button.buttonColors function to see how to customize the colors for your button.
In terms of text, the Button component gets the text style from MaterialTheme.typography.button, so you can override this field in your style to customize your button's text.
val typography = Typography(
...
button = defaultTypography.button.copy(
fontFamily = yourFontFamily,
color = Color.Yellow
)
)
For text and dimensions you can continue using XML files (res/values) and refer to them using stringResource(id) and dimensionResource(id) functions respectively.