# Migrating two decades of the Google Play app to Jetpack Compose - Speakers: Kirill Grouchnikov (Software engineer), Jon Boekenoogen (Software Engineer) - Track: Apps foundation - Video: https://www.youtube.com/watch?v=7h_kcpNGKnw - Page: https://developer.android.com/events/dev-summit/talks/migrating-two-decades-of-the-google-play-app-to-jetpack-compose ## About the talk This year, Android officially became Compose-first for building all UI, placing legacy Views into maintenance mode. For Google Play, that journey began back in 2020. Across an engineering footprint spanning nearly two decades and billions of users, migrating to Jetpack Compose was achieved through proven best practices, close collaboration with the core Compose team and continuous tooling evolution. Discover how modernizing Google Play with Compose delivered measurable improvements in velocity and long-term stability. The session walks through connecting agentic Figma design workflows with Compose implementation, automating quality to prevent unnecessary recompositions and crafting expressive interfaces with dynamic color, rich typography and layered shapes. You will leave with tested strategies to triage bugs faster and scale your own Compose migration with confidence. ## Summary - Google Play moved 97% of user impressions to Compose-only screens over six years. Major pages also saw a 30% improvement in jank rate over the past 14 months. - Incremental adoption mitigates business risks across large codebases. Teams can build familiarity on low-traffic screens first, then advance toward hosting `ComposeView` directly at the Activity root. - Dynamic theming works entirely in code using `MaterialTheme` overlays and the Material Color Utilities library. This avoids Activity teardowns, though generating color schemes at build time helps limit runtime latency. - Unidirectional data flow and declarative UI simplify architecture and remove several types of race conditions. Running data sources and UI transforms in parallel improved Google Play's load latency by dozens of milliseconds. - Automated Lint checks enforce code quality and design system rules. Play uses them to block hardcoded colors and flag potential performance pitfalls such as `BoxWithConstraints`. ## Topics jetpack-compose, migration, performance, theming, unidirectional-data-flow, lint-rules, developer-velocity ## Transcript ### Introduction and Google Play Store adoption [00:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=6s) Welcome everyone! [00:07](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=7s) My name's Jon. [00:08](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=8s) I'm joining you from the Google Play Store team. [00:11](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=11s) This is my colleague Kirill. [00:13](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=13s) We're here to talk to you today about how we moved a lot of code from the View and Fragment system over to Compose. [00:20](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=20s) Let's get started. [00:24](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=24s) So how did that adoption go and kind of what was the scale of it? [00:27](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=27s) So for the, for our app codebase, it's really grown organically since around 2007. [00:33](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=33s) We have ove-- a few million lines of production code, 200 screens, and 500 unique UI components that are used across many different of those screens. [00:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=44s) In 2020, we decided, you know, developer velocity, reliability, and performance was starting to not hit the goals that we wanted. [00:53](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=53s) So we decided to adopt a modern UI tech stack. [00:57](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=57s) We evaluated a bunch of different libraries. [01:00](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=60s) We also had some friends in AndroidX talking to us a-- to about Compose at the time, and we decided to adopt Compose and become Compose's first major partner. [01:12](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=72s) How did it go? [01:13](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=73s) Six years later, we now have 97% of user impressions within the Play Store app on Compose-only screens. [01:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=81s) All, sorry, all phone and large screen development has moved over to Compose-only since 2020. [01:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=89s) And even our extended form factors, XR, TV, Auto, Wear, have moved over to Compose since 2025. [01:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=99s) And Compose has been a really strong proponent of that success and why we've continued to adopt more and more. [01:47](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=107s) They've had a very strong, comprehensive test suite that has helped us adopt new versions of Compose twice a week. [01:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=114s) Pulling in their alpha builds and their new APIs, this helps us take advantage of new bug fixes, new performance improvements, and really explore some of those new APIs and provide new user experiences. [02:07](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=127s) Speaking of performance in particular, Compose has really delivered continued jank performance for our app across all of our major screens. [02:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=137s) This is the search results page, the home page, the details page. [02:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=141s) These are very important metrics, performance, jank, and load latency, for our business and also for our user experience. [02:30](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=150s) So we have seen a 30% improvement over the past 14 months. ### Migration plan [02:37](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=157s) But how did we get here? [02:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=158s) That's what we're really here to talk about. [02:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=159s) So let's talk about the migration plan. [02:43](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=163s) So, Play took a multi-step approach. [02:46](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=166s) We didn't want to dive all in and just put all our our eggs in one basket, so to say. [02:52](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=172s) We ended up taking a few steps to understand the risks, how it impacts our business, and really build confidence before going all in. [03:00](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=180s) So, I'll name those: crawling, walking, jogging, and running. [03:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=186s) So crawling for us was really about building familiarity with Compose. [03:11](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=191s) We started with a low traffic page. [03:13](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=193s) This could be the settings page, third party licenses, that sort of thing. [03:18](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=198s) Your main goal is to build familiarity with the Compose library. [03:23](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=203s) So this can be getting used to Rows, Columns, Text, images, all of these sorts of things. [03:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=209s) And then you need to start bringing along all of your app infrastructure and understanding how it works in the Compose way. [03:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=216s) So this could be impression logging, click logging, theming, we were revamping data loading at the same time. [03:43](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=223s) So kind of all of these things. [03:46](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=226s) And one particular thing that we found helpful was to ensure that all of our screens look consistent, we built some unit tests and functional tests to check that our theming objects in Compose mapped directly to the XML styles, typography, colors, all of these things. [04:05](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=245s) So once we built some familiarity and felt comfortable, we decided to move to walking. [04:10](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=250s) This for us was taking a smaller component on a very high traffic screen and evaluating what that sort of migration looked like. [04:18](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=258s) We would recommend taking an item that's in a RecyclerView, so that way you can understand any compatibility concerns between the View and the Compose system, and also see measure performance and jank along the way. [04:34](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=274s) The component itself doesn't have to be super complex, but you really want to have something that exercises layouts, and you know, Text, images, those sorts of things, a couple different instances of what you would normally see. [04:47](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=287s) One thing that you should keep an eye out for and probably expect in this area is some increase in jank and latency, it will be slight, but this is because you're now running two stacks. [04:57](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=297s) You have a rendering stack that is all in Compose and one that is in Views. [05:00](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=300s) So there's going to be some memory thrashing, some extra class loading, all of these things. [05:05](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=305s) But as you adopt more and more of Compose, that will go away. [05:11](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=311s) And if you really need it in the meantime, you can do some slight pre-warming of that screen to help bring it in line and mitigate that cost in the short term. [05:23](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=323s) So after walking, we move to jogging. [05:27](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=327s) So this is once we've become very, very comfortable. [05:30](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=330s) Instead of doing a single component on a high traffic page, just do the entire high traffic page. [05:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=336s) So what we did in this was we still kept the navigation library and using Fragments, but for each individual screen, it was implemented fully in Compose. [05:45](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=345s) We just had the Fragment return the ComposeView itself. [05:50](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=350s) So what you can expect is improved latency and jank. [05:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=354s) You're not having as much of that memory thrashing and class loading I was talking about. [05:59](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=359s) And Compose itself also does some levels of caching at the ComposeView level. [06:03](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=363s) So if you have a RecyclerView with a bunch of cards that look consistent and might have the same icons, all of that icon parsing and resource usage is shared amongst everything in the ComposeView. [06:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=377s) So after that, we moved to running. [06:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=381s) So this is when you really go all in. [06:24](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=384s) Once you have the majority of your traffic from your app on Compose, we moved up one level. [06:31](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=391s) So above screens, we have Activity. [06:34](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=394s) So move the ComposeView to the Activity root. [06:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=396s) And a lot of your code will look relatively similar. [06:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=399s) You're still trying to do a navigation instruction where you want to say, move from one screen to the other. [06:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=404s) But instead of doing Fragment transactions, you're cause-- calling a Composable function. [06:50](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=410s) And you can do this on top of the Nav2 library or the Nav3 library, whichever you choose. [06:56](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=416s) For us, we didn't have all of our screens migrated. [06:58](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=418s) I did mention we have a lot of them. [07:01](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=421s) So we had built Fragment compatibility where you can have a Fragment-based screen and leverage Compose's AndroidFragment wrappers to do this. [07:10](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=430s) What you can expect again, again, jank and latency is important to our business, so we're just kept pushing that envelope and you keep seeing that to improve cuz now we have caching across screens and a lot less of this memory thrashing problem. [07:24](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=444s) But you also get new capabilities. [07:26](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=446s) We have an example of it on the right in this GIF where you can do shared element transitions between screens. [07:32](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=452s) So we have a image that we are blowing up as you move from one screen to the other and it provides that continuity. [07:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=459s) You can also leverage all of Compose's animation flexibility while you're doing that whole transition. [07:49](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=469s) And with that, I will hand it over to Kirill to talk about some more capabilities. ### Theming and performance [07:53](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=473s) Thank you, Jon. [07:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=474s) I want to talk a little bit about a topic that is near and dear to my heart, which is theming and styling for Android apps, something that I've been doing for a while now. [08:03](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=483s) In the View world, there was Java code and a little bit later Kotlin code, and then there were X-- XML resources, resources such as colors, strings, dimensions, layouts, and themes and styles. [08:19](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=499s) And the way it worked was that all these XML resources were compiled to a resource table that was included in the APK, and it was pretty much static. [08:28](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=508s) There was there is an API `Resources.Theme.applyStyle` that you could use dynamically at runtime to apply custom styling on top of the UI hierarchy. [08:40](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=520s) The major limitation was or is that you have to do it before inflating your content, which means that if you want to dynamically apply that styling on an existing piece of UI, you had to tear down and recreate the whole Activity, which was not great for the user experience. [08:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=534s) Also, since it was applying this pre-compiled XML blocks, it was becoming kind of like difficult to combine multiple conditional dynamic overlays on top of each other based on your experiment flags or any other business needs. [09:11](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=551s) It was kind of difficult to build a proper mental model of the style hierarchy in that XML world. [09:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=557s) And finally, there was no single unified view in Android Studio or any other development tool that allowed you to kind of like bridge the gap between the Java world, the code world, and the XML world. [09:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=569s) And it was kind of like difficult to juggle and build the proper mental model and debug why my style is not getting applied, why am I getting the wrong color, and so on. [09:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=578s) In the new world, in the Compose world, specifically for the Material Design system and the Compose Material library, but also for other design systems out there, what we have is for the Material Design system, we have a MaterialTheme Composable that you apply to the very top of your UI hierarchy on your screen, and you can also apply this MaterialTheme as an overlay on any smaller piece of your UI. [10:02](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=602s) If you look on the screen on the right, you can see this Play Points Gold up top, which is a little bit which is a little card here with the progress indicator and a couple of texts. [10:12](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=612s) We apply this Material Theme overlay with a custom color scheme generated by the Material Color Utilities library, available on GitHub for you to use. [10:22](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=622s) We pass in kind of like this brownish, yellow, gold color as the seed, and it generates the full spectrum of material color tokens, primary on primary, secondary on secondary, that we then use inside the implementation of this card. [10:37](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=637s) And this brings the full dynamic theming capability to the hands of Android developers. [10:45](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=645s) It is all done in code, it is all done in Kotlin, which means that it's much easier to build the proper mental model of how it all operates, and it's much easier to debug. [10:56](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=656s) And internally, this overlays are attached as composition locals to the specific UI nodes in your hierarchy, where you choose to apply them. [11:07](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=667s) Performance considerations: generating a full color scheme with Material Color Utilities library is not free. [11:13](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=673s) It does come with a certain overhead. [11:15](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=675s) So, if you have something complicated enough that needs multiples of this, some sort of a bento box, or whatever it might be, depending on your product needs, and you have multiple color schemes that you generate at runtime, that starts adding up, that starts adding to latency and jank. [11:33](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=693s) So, a, you want to start measuring how it affects your performance, and b, you can offload some of this overhead from runtime to build time. [11:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=704s) You can use the same Material Color Utilities library at build time to pre-generate those color schemes based on your branding needs, or based on your product needs, and offload this overhead to a build time. [11:57](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=717s) For those composition locals that I mentioned that keep track of where exactly the overlays are in the UI hierarchy tree, those are much cheaper, but still not free. [12:08](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=728s) So, depending on how many of those you use, dozens, hundreds, it starts adding up. [12:13](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=733s) And at some point, you do want to start looking at the cost of all of this combined composition locals. [12:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=741s) And this is an example of what I was mentioning kind of like true dynamic runtime theming capabilities of Compose. [12:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=749s) Here we have four quadrants, the same visuals, the same let's call it an event card. [12:35](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=755s) We have this hero image that spans the entire card, and then we have a label in the top left, a button in the bottom right, and a couple texts. [12:45](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=765s) And on the right-hand side of each one of the quadrants, you see the call out of the color tokens that are used as a design spec for each one of the elements. [12:55](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=775s) The card implementation is exactly the same for all this four. [12:59](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=779s) The only difference is the Material Theme overlay that wraps the card is generated or configured with a dynamically created color scheme based on the dominant color of the image: blue, purple, yellow, and red. [13:15](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=795s) Everything else remains the same. [13:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=797s) You don't need to tear down and recreate your activity. [13:19](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=799s) You can apply it conditionally based on your experiment flags, or any other business needs. [13:25](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=805s) And it's a much more dynamic, as I said already, it's a much more dynamic, truly dynamic world of applying color customizations to your app. [13:35](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=815s) And now back to Jon for the next section. ### Scaling reliability and velocity [13:37](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=817s) Yeah. [13:37](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=817s) So, that's some more of flexibility that Kirill was talking about. [13:41](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=821s) Let's talk about reliability and velocity. [13:43](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=823s) So, building high quality code quickly is something that all developers and businesses are looking for. [13:49](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=829s) So, let's see how Compose helped enable Play to do that. [13:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=834s) So, first and foremost, I know big diagram, first and foremost simplicity of architecture has huge benefits for all code authors and maintainers, humans and agents alike. [14:05](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=845s) I'm not going to dive too deep into this, but one of the mental model things that Compose tries to put out there is around unidirectional data flow, and it's really had a big impact on our team. [14:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=857s) For those unfamiliar with how it works, the basics are you have a data source. [14:22](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=862s) A data source provides data. [14:24](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=864s) That data might be transformed to a specific UI, which we output as a UI model. [14:30](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=870s) And then Compose can easily observe that by just calling a value property on that state. [14:35](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=875s) This is kind of the arrows along the top side of this diagram. [14:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=878s) And then events flow the opposite way. [14:40](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=880s) So, if a user clicks on a button, this will go back to the UI transformer, updates the data source emits a new value, transforms run, and you get this loop. [14:50](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=890s) So everything kind of runs through this relatively simple loop. [14:55](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=895s) The benefits that we've really seen from this is it helps reduce ramp-up time. [15:00](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=900s) There's no more like where are all of these calls happening to the main thread or off of these other threads. [15:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=906s) It follows one general loop. [15:08](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=908s) Compose makes data observation super straightforward, and data has strong consistency guarantees across architectural layers. [15:18](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=918s) The mental model has reduced many conversations about sources of truth within our team and fully removed several types of race conditions altogether. [15:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=929s) Another huge benefit for us is compared to our previous model-view-controller architecture we had, it became very, very straightforward to have parallel execution of data sources and UI transforms. [15:42](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=942s) So, this really improved our load latency within the Store by, you know, dozens 50 ms or something like that. [15:49](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=949s) It was pretty impressive for us. [15:54](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=954s) Paired with unidirectional data flow, the other huge proponent of Compose is around declarative UI. [15:59](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=959s) Kirill touched on this a bit. [16:01](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=961s) So, Play developers have really enjoyed this as well. [16:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=966s) UI control flow is now in a single file. [16:09](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=969s) We have an example on the right which is fairly straightforward. ```kotlin @Composable fun PurchaseRow(onClick: () -> Unit, modifier: Modifier) { Row(modifier) { Text("20% off sale") if (LocalExpts.current.useButtonV2) { ButtonV2("Buy", onClick = onClick) } else { ButtonV1("Buy", onClick = onClick) } } } ``` [16:12](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=972s) We're changing between two button implementations based off of a experiment flag. [16:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=977s) Instead of needing to read across multiple view files or, you know, fragment files and XML files, it you just read it top to bottom, and it's made it much simpler. [16:28](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=988s) The cost of nesting composables is also much, much lower than nested view hierarchies, and it's allowed us to naturally arrange our code to make it more readable instead of needing to eke out every little bit for performance. [16:42](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1002s) Other benefits we've seen is the life-cycle callbacks have been a lot more simpler and been reduced in how many you need to worry about. [16:53](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1013s) So, instead of having view life-cycle, fragment life-cycle, activity life-cycle, you get disposable effect, you get side effect, and depending on where you place it just acts in a very consistent way. [17:03](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1023s) So, that's been a huge benefit for us. ### Setting the stage for LLMs [17:08](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1028s) So, these two architectural changes really set the stage for LLMs in my opinion and have enabled it to generate pretty high-quality code. [17:18](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1038s) And I'm sure a lot of you have probably tried this or done this with your favorite LLM. [17:23](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1043s) You might take a screenshot that's annotated with colors and typography and feed it in and see, you know, it can generate rows and columns and text and buttons, and it does a reasonable job. [17:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1056s) It does have its limitations, though. [17:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1059s) And what Play has really seen is it doesn't understand your codebase, right? [17:42](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1062s) It doesn't know what components you've already built that it should be reusing, doesn't know design token names. [17:48](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1068s) It just gives you hard-coded colors and strings. [17:51](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1071s) What what content should be from the device, what content should be coming from a server, or how do I make sure it's performant in using LazyList and not just making a huge column? [18:02](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1082s) So, what we've done to kind of address some of these is annotating those screenshots with a little bit more metadata. [18:10](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1090s) So, UI names, bounding boxes so we know where components start and end, design tokens for the semantic names of colors and typography, skills and also some prompting to say where this is going to be placed so it can take those performance considerations into account. [18:28](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1108s) And then also just a very high-level function signature, so you can tell it what content is dynamic versus static. [18:35](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1115s) So, you end up with something like what we had before, moving over to after. [18:41](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1121s) I don't have the bounding boxes here because it would add a lot of noise to the diagram. [18:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1124s) But you can see those hard-coded colors and typography names being replaced with title large and on surface. [18:51](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1131s) We have the component names like card header and icon badge clearly marked. [18:56](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1136s) So, that way, those can be found in your codebase. [18:58](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1138s) And for us, we have, you know, a lot of these are coming from common design libraries as well, which Kirill will talk about in a minute. [19:07](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1147s) So, you're not custom labeling all of these all the time. [19:10](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1150s) You pair that with a method signature like we have at the bottom, `OfferCard`, and then it knows, you know, the title, the `endDate`, the image are all dynamic. [19:19](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1159s) We shouldn't hard-code those. [19:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1161s) And along with those semantic names, we can now swap into all of those different color applications that Kirill was talking about. [19:31](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1171s) And with that, we're generating a lot more code. [19:33](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1173s) I'm going to talk hand it back to Kirill to talk about how we're upholding quality. ### Quality automation [19:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1178s) Right. [19:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1178s) Thank you, Jon. [19:39](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1179s) So, continuing on this topic of LLM and agentic- assisted development, we first start on the UX design side. [19:47](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1187s) What you want to do is, whatever is your design tool of choice, maybe Figma, maybe something else, you want to have a shared template that is used by all the designers that has color tokens, typography tokens, shape tokens from your design system of choice. [20:02](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1202s) Maybe it's Material. [20:03](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1203s) Maybe it's your custom designer system. [20:05](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1205s) And you want specifically for the color tokens, you want to have those templates have built-in support for switching between light and dark mode and for switching between normal, medium, and high contrast modes that were introduced at the Android level a few years ago. [20:21](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1221s) So that the designers can quickly switch between these different combinations to see how their design specs respond to that. [20:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1229s) Then, as it is being finalized and handed over to the implementation side, in that export pipeline, you want to have some sort of capability to track that every color, every typography element, every shape is using a matching token instead of being a hardcoded RGB value, or hardcoded font family, or font size. [20:52](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1252s) And finally, what we found very useful at the scale of Google Play, we have, not even dozens, we have a few hundred components, clusters, cards, containers that are used across a variety of screens that we have. [21:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1266s) What we found useful is to keep track of all of this in a single place, what we call a component catalog, and then have a custom agent trained on that component catalog. [21:16](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1276s) And where it comes useful is when you start a new screen, maybe a new user journey, maybe a new product area, and from those initial design explorations, maybe slides, maybe Figma spec, you can feed that into that agent, and the agent helps quickly identify which components already exist so that they don't need to be redesigned and respec'd, so they don't need to be reimplemented from scratch. [21:41](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1301s) And that cuts down quite significantly on the design costs and on the implementation costs. [21:48](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1308s) On the implementation side of things, we lean very heavily on core and custom Lint rules. [21:52](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1312s) A couple of core Lint rules that I want to call out are FrequentlyChangingValueDetector that helps identify sources of unnecessary recompositions, which can lead to latency and jank, and the other one is ScaffoldPaddingDetector for flagging cases where we use the Material Compose Scaffold composable, but ignore the incoming paddings. [22:16](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1336s) It's not a source of latency and jank, but, in some cases, it can lead to very difficult-to-debug, kind of like, layout misalignments in certain cases where, like, it's edge-to-edge, or folding, unfolding. [22:29](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1349s) And, finally, BoxWithConstraints, it comes with certain performance considerations. [22:33](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1353s) It might take two frames to properly lay out everything. [22:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1356s) We don't block it outright, but we do flag it as a Lint warning so that the developer that is working on that particular screen knows that they are making a specific choice to use BoxWithConstraints, or maybe they can choose to use something else for that specific layout. [22:53](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1373s) For the theming automation, again, color tokens, typography tokens, shape tokens. [22:59](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1379s) We have a Lint rule that blocks the usage of hardcoded colors and hardcoded typography. [23:05](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1385s) We have a Lint rule that flags usage of Material 2 components. [23:09](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1389s) We only want to use Material 3, the latest and greatest, including the new expressive components that were introduced last year and this year. [23:18](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1398s) We want We have a custom Lint rule that prevents people from reading colors from, let's say, legacy XML files. [23:25](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1405s) We want to be Compose-first and Compose-only for new screens. [23:30](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1410s) And, Text composables, specifically in the Material design system, they have fallbacks for style and color. [23:38](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1418s) They read it from local content color and local typography, but we don't want to rely on that. [23:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1424s) We want every single text to be intentionally using the specific style, typography style, and the specific color from the design spec. [23:52](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1432s) So, that's a Lint rule that we have. [23:53](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1433s) And the last one, along the lines of that ScaffoldPaddingDetector, we have a custom Lint rule that enforces that every single Composable function that we have uses its input modifier. [24:06](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1446s) Again, to prevent like, you know, new usages from being like incorrectly placed, or not respecting the incoming background, or whatever it might be. ### Conclusion [24:17](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1457s) And these are the three main points that we would like you to take out of this presentation. [24:25](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1465s) First one is that today, at the end of 2026, in our opinion, Compose is a mature UI toolkit, proven at scale. [24:33](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1473s) Android itself is Compose-first. [24:36](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1476s) Earlier this year at Google I/O, we announced that all the UI libraries and all the agentic flows are Compose-first. [24:44](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1484s) And, as Jon mentioned earlier in the presentation, crawl, walk, jog, and run, we suggest that you migrate incrementally, especially large codebases, to accelerate your velocity for developer flows and for the agentic flows. [25:01](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1501s) And, with that, we thank you for sharing your time with us today, and in a few minutes, myself and Jon will be outside to take any questions that you have. [25:11](https://www.youtube.com/watch?v=7h_kcpNGKnw&t=1511s) And, again, thank you for your time.