Bryan Herbst
10/21/2020, 2:44 PMMaterialTheme.Typography
), but I’d like to lean on Material for a number of components such as Button
.
Specifically looking at Button
, I’d like to use my own FancyTheme.typography.button
instead of MaterialTheme.typography.button
.
My first thought was to do something like
@Composable
fun FancyButton(content: @Composable RowScope.() -> Unit) {
Button {
ProvideTextStyle(
value = FancyTheme.typography.button,
children = content
)
}
}
But that doesn’t quite work since ProvideTextStyle
’s content doesn’t accept a RowScope
receiver.
Is there a better way to do this?Row
in FancyButton
, but that doesn’t seem ideal either.Zach Klippenstein (he/him) [MOD]
10/21/2020, 2:52 PM@Composable
fun FancyButton(content: @Composable RowScope.() -> Unit) {
Button {
ProvideTextStyle(FancyTheme.typography.button) {
content()
}
}
}
Bryan Herbst
10/21/2020, 2:55 PMZach Klippenstein (he/him) [MOD]
10/21/2020, 2:56 PM@Composable
fun FancyButton(content: @Composable RowScope.() -> Unit) {
ProvideTextStyle(FancyTheme.typography.button) {
Button(children = content)
}
}
Bryan Herbst
10/21/2020, 2:59 PMProvideTextSyle
Zach Klippenstein (he/him) [MOD]
10/21/2020, 3:00 PMAlexjlockwood
10/21/2020, 6:50 PMButton
.
(1) require that the caller to pass in a Text
with a text style explicitly specified in its style
argument (which will ensure that material will not overwrite the text style)
(2) haven’t tested this, but i think you could also re-wrap the Button
in a new MaterialTheme
with a different Typography
palette specified. so if Button
internally uses MaterialTheme.typography.button
, then you would wrap the Button
in a new MaterialTheme
that maps Typography#button
to the text style you use in your design system.
i personally don’t like either of these options tbh. (1) is not ideal because people are not going to remember to do this (and even if they did, it’s kind of tedious), and (2) feels like i’m hacking around an implementation detail in the material compose library that could suddenly change under my feet.
what i’ve ended up doing in a many cases is just writing my own components from scratch, using the material compose library source code as a guide. i will say, it actually hasn’t been that bad… i’ve forked a pretty small amount of code so far. but i guess it really depends on how different your design system is from material (our design system is different enough that these hacks around material were not worth it)String
instead of a generic Text
composable. that would give you more control over the creation of the styled text, but also would make the component less flexible since you’d be removing the Slot-based APIBryan Herbst
10/21/2020, 7:07 PMMaterialTheme
the closer I’m getting to discarding MaterialTheme
completely and basically forking the Material components.
It’s unfortunate because I think the Material components get me 75% of the way there, but that last 25% is different enough that it will turn out to be worth the tradeoff.Alexjlockwood
10/21/2020, 7:16 PMRippleIndication
it would be nice if there was some guarantees that the material compose library could provide. for example, maybe a convention that MaterialTheme
properties could always be customized (by using them as default arguments in public-facing component APIs). otherwise you end up having to hack around provider-statements that are used internally within their components with no great way of safely overriding them.
(cc @nickbutcher who i’ve been talking to a lot about this as well 😄)Bryan Herbst
10/21/2020, 7:21 PMMaterialTheme
-provided properties!
Would love to see replacing MaterialTheme
become easier. JetSnack’s hack of changing all the colors to an obnoxious color to discourage use is certainly a clever way of going about it, but I think it highlights the need for a more cohesive approachAlexjlockwood
10/21/2020, 7:27 PMMaterialTheme
at all and it’s been not so bad so far. luckily a lot of the more obscure/non-finalized stuff like accessibility (well maybe not for you 😉) is encapsulated in things like Modifier.clickable
and Modifier.toggleable
in foundation… so `Button`s end up being basically a Box
with a clipped shape and a colored background color. it’s been a nice learning experience too. 😄Louis Pullen-Freilich [G]
10/21/2020, 8:22 PMAmbientContentColor
and AmbientTextStyle
being foundation primitives, when they are realistically Material theming ambients that live in the wrong layer. These will be moved to material soon along with the existing Text
component, which makes this separation more clear and leaves design system related ambients out of foundation, leaving un-opinionated components (similar to the split with TextField
and BaseTextField
).
For this use case when you have a different type scale, you can easily create a FancyText
that uses a FancyTextStyle
, and just use that in your Button
wrapper instead. Part of the complexity here is because you are trying to introduce a separate type scale alongside the existing Material one - if you forget to provide the text style then you will actually be using the Material type scale, which can be easy to miss.
Some other miscellaneous thoughts:
oh also, with approach (1) you could potentially make things a bit better by requiring the caller to pass in aIt's true that this becomes less flexible, but if you are creating components for use in your app (as opposed to a publish component library) then this is probably a positive - it means that you can be opinionated and guarantee the same UI across all usages of your components, and make the call site more simple and easy to use. For example, you could consider accepting aninstead of a genericString
composable. that would give you more control over the creation of the styled text, but also would make the component less flexible since you’d be removing the Slot-based APIText
R.string
resource or similar representation so that the Button
itself can do the localization.
what i’ve ended up doing in a many cases is just writing my own components from scratch, using the material compose library source code as a guide.This is actually an explicit design goal of the material / foundation libraries. Material is not the design system, it's a particular implementation of a design system - if your design system differs then creating your own type safe, semantically meaningful theme with corresponding components is absolutely the correct choice here.
it would be nice if there was some guarantees that the material compose library could provide. for example, maybe a convention thatIn general we try to follow this for colors for example, every color pulled from the theme is just an override-able default. I think a text style is harder / more confusing to expose in the API, since there may not be anyproperties could always be customized (by using them as default arguments in public-facing component APIs).MaterialTheme
Text
inside to actually consume the text style. For these use cases creating a wrapper Button
that provides your custom text style seems reasonable too.
so `Button`s end up being basically aYup! We try to layer things so it is easy to copy and paste and drop down to the layer relevant to you, these abstractions exist to encourage everyone to build meaningful components. The goal is to make the bar so low that in most cases it is easier to build your ownwith a clipped shape and a colored background colorBox
FancyButton
than trying to hack around with ambients to override Material opinionated parameters that aren't really intended to be changed.Bryan Herbst
10/21/2020, 8:44 PMProvideTextStyle
.
I do think that more generally it is a common theme with colors as well as typography as Alex noted.
My concerns with going the re-implementation approach are:
• Education & awareness - how do I get engineers on my team to use FancyText
instead of Text
? Most examples are going to use Text
, so I think it’s natural for engineers to gravitate towards that. This is an issue with both wrapping and copying, but worth noting.
• Mixing Material and custom components - There are some Material components that I don’t think I’ll need to (or would want to) modify. For example, ModalBottomSheetLayout
. One easy solution to the education & awareness issue is to avoid exposing the androidx.compose.material
library to our app code, but that makes using components we can use more difficult
• Updates to components- It is easy to copy & paste Button
today, but if a future version of Material makes changes to Button
I need to know about those changes and then actually put in the effort to pull in those changes. I’m thinking more than just visual changes- things like accessibility improvements are likely to happen in the future.
• New components - If we go the route of re-implementing the Material components, we inevitably will reach a point where Material introduces a new component that an engineer wants to use. I’d much prefer it is was usable (or easily wrappable) out-of-the-box over copying and pasting.Alexjlockwood
10/21/2020, 8:47 PMIn this specific case, is there something you find off-putting about creating a wrapper that provides your own text style internally?i think it’s mainly just the fact that i can’t be sure the material design spec will change some day w/o me realizing it. for example, i could wrap around
ListItem
and create my own MaterialTheme
wrapper that maps our custom text style CoreUiTheme.typography.bodyF1
to MaterialTheme.typography.body2
for the purposes of customizing the list item’s secondary text, but it’s sort of ambiguous whether usage of this specific material text style is an implementation detail or part of a public facing API that will never change.
Yup! We try to layer things so it is easy to copy and paste and drop down to the layer relevant to you,so far my experience creating my own components has been relatively painless. i think a big reason is the separation of foundation and material. it sounds like you are focusing a lot on moving as much core-logic as possible into foundation, which is definitely appreciated!
Updates to components- It is easy to copy & pasteand yeah, this is in truth the biggest annoyance ive faced so far. but i guess i can’t blame anyone but myself for getting into compose during the alpha stages 😂 in the meantime, i’ve been trying to figure out the best way to keep up-to-date with the changes that are happening internally each week (reading changelogs, browsing git history of the material source code every once in a while, etc.). i’ve also been putting off more complex components like text field which sound like they are being revamped w/ a new focus api or something (according to issuetracker at least 😄)today, but if a future version of Material makes changes toButton
I need to know about those changes and then actually put in the effort to pull in those changes.Button
Louis Pullen-Freilich [G]
10/21/2020, 10:03 PMOne easy solution to the education & awareness issue is to avoid exposing theYep, for larger apps this is the intended / recommended approach. It lets you expose only the components you want to expose, and ensures consistency - but as you mentioned it means you would need to wrap every component. Probably a good thing for large apps (so you can guarantee that the colors are correct, etc) but it is a non-trivial amount of boilerplate for smaller apps.library to our app code, but that makes using components we can use more difficultandroidx.compose.material
Updates to components- It is easy to copy & pasteThat's true - I think copy and pasting is for the exceptional case where you have a very not-Materialtoday, but if a future version of Material makes changes toButton
I need to know about those changes and then actually put in the effort to pull in those changes. I’m thinking more than just visual changes- things like accessibility improvements are likely to happen in the future.Button
Button
- in most cases since you can override the background color and shape, all that's left is the default size (which you can also override with modifiers) - so just wrapping the underlying Button
should shield you from these sort of changes.
New components - If we go the route of re-implementing the Material components, we inevitably will reach a point where Material introduces a new component that an engineer wants to use. I’d much prefer it is was usable (or easily wrappable) out-of-the-box over copying and pasting.If your design system has custom colors / typography / shapes, then the default out-of-the-box component may not match your design system anyway, so it may require some pre-work from your side anyway. In any case for most Material components, some / a lot of the customization parameters might not be desired for an app, for example you might not want developers to be able to customize the shape of a
Button
- so having a wrapper makes it easier to expose only the things you actually want to expose, as well as setting the correct.
but it’s sort of ambiguous whether usage of this specific material text style is an implementation detail or part of a public facing API that will never change.Ah I see, yep this is an interesting question. Default ambient values inside a component are not much different from default parameters though in that they may change. You can / should probably avoid this by using your own theming ambient that you know won't change to express this contract, instead of proxying your system -> Material.
Bryan Herbst
10/22/2020, 1:18 PMLouis Pullen-Freilich [G]
10/22/2020, 1:41 PMPrashast Rastogi
05/16/2021, 7:44 AMFancyTheme.typography.button or FancyTheme.typography.text1
Bryan Herbst
05/17/2021, 12:40 PMobject FancyTypography {
val text1 = TextStyle( /*...*/ )
val button = TextStyle( /*...*/ )
}
Tash
10/05/2021, 6:39 PMBryan Herbst
10/05/2021, 8:26 PMTash
10/06/2021, 8:23 PMBryan Herbst
10/06/2021, 8:27 PMJust curious, did y’all go the “Jetsnack route” and override all MaterialTheme “tokens” with a “debug color”Nope - Using anything from the Material Compose packages is a lint error in our codebase, so that isn’t a problem :)
Kinda related, is your design system/theming system extensible in the same way MaterialTheme is?Nope - we only have a single app with a single design system, so we don’t need to support configuring colors, shapes, typography, etc.
Tash
10/07/2021, 2:58 AMUsing anything from the Material Compose packagesi see, so its a complete replacement 👍🏼
we only have a single app with a single design systemthats great, keeps the implementation from getting problematic if any type of “sub-theming” needs to be done