Skip to content
Source

About Material 2 to Material 3 Migration

We often say that we have our own design system in the project. Actually, it's ours, of course, partly. Essentially, we abstracted from the product developer the components and tokens they work with. That is, if before they just took buttons and texts from Material 2, now they take them from the design system module.

But the implementation hasn't gone anywhere. Button, Text or a conditional Scaffold from our design system is a wrapper for analogs from M2. Only the token system is entirely ours and a number of new components like avatars. We don't make everything ourselves because the designers are sweethearts and take Material components as a model; they look almost the same. Plus we don't believe we'll do well if even Google couldn't.

Designers started moving their components to M3, which is very noticeable in things like switches and loaders. We, accordingly, want to:

  • update the design to new layouts
  • get inset support out of the box
  • various calendars, date pickers and other new components
  • and much more that will never be in M2

Google has documentation for migrating from M2 to M3, but the steps there look roughly like in the meme about drawing an owl. They say you can keep both M2 and M3 in the project simultaneously, but carefully and, preferably, not for long. Gradually migrate components from M2 to M3 this way, and finally abandon the first one. Fair enough.

The difficulty, obviously, is in this "gradually". We immediately decided to just decompose this task into migrating individual components, and at first it went well. And then somewhere halfway we migrated m2.Text to m3.Text and realized that you can't do that.

Text, like some other components, is tied to some specific CompositionLocal from the theme. For this to work with an M2 component, we passed m2.LocalContentColor and m2.LocalTextStyle. We thought this through in advance and started passing m3.LocalContentColor with m3.LocalTextStyle from the theme too. And you think everything worked? Well yes, at first glance, the Text component where we used it was fixed.

But there's a nuance. Other components, for example TextField or BottomNavigationItem, which we haven't gotten to yet. It turns out they also override m2.LocalContentColor and m2.LocalTextStyle in their depths to customize the floating placeholder in the text field or tab name (read m2.Text inside). You start using m3.Text inside, m2.TextField overrides m2.LocalTextStyle inside. Accordingly, m3.Text inside doesn't adapt to this at all because it expects m3.LocalTextStyle.

(And there are also hardcoded tokens from M2 and fonts, for example, and fonts never worked that way for us, but that's another story)

Hence the conclusion. You can't just take and migrate components one by one from second material to third. The algorithm is as follows:

  • take the next component
  • dig into its guts down to foundation itself
  • if you see overriding or using CompositionLocal from the theme - move to the end of the queue
  • take on the next one
  • and then hope to rewrite everything left over at once. There's a chance it'll work.

Or there's a more logical but difficult option. By the glorious tradition, we extract the guts of M3 components outward. And in this process, everywhere there's CompositionLocalProvider - we provide both M2 and M3 versions of locals. When we finish the full transition, probably the guts can be tucked back in.

PS. Our faces when rumors from Google leaked this week that they're preparing a new version of material?