Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compose/snippets/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ dependencies {
implementation(libs.androidx.compose.material)

implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.material.ripple)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationStyleApi::class)

package com.example.compose.snippets.styles

import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.style.ExperimentalFoundationStyleApi
import androidx.compose.foundation.style.MutableStyleState
import androidx.compose.foundation.style.Style
import androidx.compose.foundation.style.styleable
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextOverflow

@Composable
fun BaseCheckbox(modifier: Modifier = Modifier, style: Style = Style) {
// Your custom component applying the style via the styleable modifier
}

@ExperimentalFoundationStyleApi
@Composable
fun BaseText(
text: String,
modifier: Modifier = Modifier,
style: Style = Style,
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
autoSize: TextAutoSize? = null,
) {
BasicText(
text = text,
modifier = modifier.styleable(null, style),
onTextLayout = onTextLayout,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
autoSize = autoSize,
)
}

// Example of using a Style within your own component.
@ExperimentalFoundationStyleApi
@Composable
fun BaseButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
style: Style = Style,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember {
MutableInteractionSource()
},
content: @Composable RowScope.() -> Unit
) {
val styleState = remember(interactionSource) {
MutableStyleState(interactionSource)
}
styleState.isEnabled = enabled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating state like styleState.isEnabled directly within the body of a composable is a side-effect and can lead to unpredictable behavior. This mutation should be performed within a LaunchedEffect to ensure it happens in a controlled manner only when the enabled value changes.

    LaunchedEffect(enabled) {
        styleState.isEnabled = enabled
    }

Row(
modifier = modifier
.hoverable(interactionSource = interactionSource)
.focusable(true, interactionSource = interactionSource)
.clickable(
enabled = enabled,
onClick = onClick,
interactionSource = interactionSource,
indication = null,
)
.styleable(styleState, StylesThemes.LocalAppStyles.current.baseButtonStyle, style),
content = content,
verticalAlignment = Alignment.CenterVertically
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationStyleApi::class)

package com.example.compose.snippets.styles

import androidx.compose.foundation.style.ExperimentalFoundationStyleApi
import androidx.compose.foundation.style.Style
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

private object StyleDosDonts {

// [START android_compose_styles_basics_do_expose]
@Composable
fun GradientButton(
modifier: Modifier = Modifier,
// ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
style: Style = Style
) {
// Consume the style
}
// [END android_compose_styles_basics_do_expose]

// [START android_compose_styles_basics_do_replace]
// Before
/*
@Composable
fun Button(background: Color, fontColor: Color) {

}
*/
// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun StyleButton(style: Style = Style) {

}
// [END android_compose_styles_basics_do_replace]

// [START android_compose_styles_basics_do_wrappers]
/*
@Composable
fun Button(modifier: Modifier = Modifier,
style: Style = Style) {
// Uses LocalTheme.appStyles.button + incoming style
}
*/

// ✅ Do create wrapper composables that expose common implementations of the same component
@Composable
fun WrapperGradientButton(
modifier: Modifier = Modifier,
style: Style = Style
) {
// Uses LocalTheme.appStyles.button + LocalTheme.appStyles.gradientButton + incoming style - merge these styles
}
// [END android_compose_styles_basics_do_wrappers]

// [START android_compose_styles_basics_dont_layout]
@Composable
fun FavouriteScreen(
modifier: Modifier = Modifier,
// ❌ DONT add style parameter to Screen-level composables
style: Style = Style,
// etc
) {

}

@Composable
fun CircularCustomLayout(
modifier: Modifier = Modifier,
// ❌ DONT add style parameters to composables where the main function is layout.
style: Style = Style
) {

}
// [END android_compose_styles_basics_dont_layout]
}
Loading
Loading