Is there a way to use `staticAmbientOf()` / `ambie...
# compose
b
Is there a way to use
staticAmbientOf()
/
ambientOf()
to provide an
@Composable
without a factory layer like
AmbientIndication
uses? Specifically my use case is providing my own version of
AmbientColors
, but all of my theme’s colors come from resources for interop with our existing View-backed content. The colors are defined like so:
Copy code
object ColorPalette {
  @Composable val red: Color get() = colorResource(R.color.red)
}
Since those colors are
@Composable
, I can’t just do something like
val primaryColorAmbient = staticAmbientOf { ColorPalette.red }
z
Just provide the whole
ColorPalette
in the ambient. Might want to make it an interface then as well.
b
That was a bit of a simplified example, what I actually have is a class similar to
MaterialColors
that contains all the theme colors, but the default values for those colors all come from
ColorPalette
So at the end of the day an expanded version of the ambient would look like this:
Copy code
val AmbientColors = staticAmbientOf { lightColors() }

@Composable
fun lightTheme() = ThemeColors(
  primary = ColorPalette.Red,
  secondary = ColorPalette.Blue,
  // etc
)
Basically the same overall structure a
MaterialColors
and
MaterialTheme
, with the key difference being that the default colors for each theme are defined from resources and thus are Composable
I don’t think providing the palette in an ambient would help, because the
AmbientColors
would still have to actually resolve the color resources
The way
AmbientIndication
handles it is to provide a Composable itself, but I don’t want to have to resolve the color every time I need to use it- I’d like to access it as a property like we can with
MaterialColors
z
I don’t want to have to resolve the color every time I need to use it
Why not? I believe Android caches resources so you’re not actually looking it up from the apk every time, just hitting a map
If you really do need to cache yourself though, you do something like:
Copy code
object ColorPalette {
  private var _red by mutableStateOf<Color?>(null)
  @Composable val red: Color get() = _red ?: colorResource(R.color.red).also { _red = it }
}
But that is redundant for this particular example, since resource caching. But it could be helpful if you’re doing some other expensive calculation on that resource as well
b
I mostly dislike it for the verbosity it adds to defining my colors, but that’s definitely something I’ll consider. Thanks!
z
Again, your original code snippet is what i would use.
a
have you checked out
MdcTheme
? https://github.com/material-components/material-components-android-compose-theme-adapter/blob/develop/lib/src/main/java/com/google/android/material/composethemeadapter/MdcTheme.kt this is basically the approach i ended up using. so my entire color palette thing is just
Color
objects. and then i extract the “themeable” colors w/ the theme attributes we use in pre-compose UI. the caching happens in the theme (basically i
remember
the current color palette and recompute it only when
ContextAmbient.current.theme
changes)
a couple other things worth noting: •
Color
is an inline class, which means at compile time it just becomes a
long
(which is really nice/efficient). • As soon as you make it nullable like
Color?
, that
long
becomes a
Long
and introduces some additional overhead (auto-boxing) • Also FYI,
colorResource()
doesn’t internally use
AppCompatResources#getColorStateList()
which means it won’t understand theme attributes in color state lists on lollipop. • I haven’t profiled the performance hit of resolving colors each time you access the palette, but i’d bet that caching it a single time in the top-level theme is probably the more efficient approach (although zach may be right that the difference is negligible 🤷 ).
b
Thanks Alex! Definitely gives me a few ideas on how else to approach this. Unfortunately our existing theme is pretty lacking in terms of a cohesive design system, so the bridge between what I’m trying to establish in Compose and what exists in the View toolkit is a little shaky. I’d still really like to be able to either make a Composable ambient or
remember
a composable, but I also totally get why that isn’t a thing. I think I just need to tweak the way I’m initializing my colors to not be Composable