Conf42 Mobile 2022 - Online

From XML to Compose, my journey of transforming an existing large app to Jetpack Compose

Video size:

Abstract

After Jetpack Compose was finally released, creators of new Android apps were now equipped with a tool that will help them easily write Android apps using the new declarative approach. The journey is not easy for developers who already have large apps that use the imperative, XML-based approach and want to convert it to Jetpack Compose. The migration journey has its ups and downs, initially daunting but eventually satisfying.

This talk will highlight that journey, Ahmed’s learnings and advice for developers looking to migrate their existing Android apps to Jetpack Compose.

Summary

  • Ahmed Tikiwa talks about transforming an existing Android app to jetpack compose. Your views are now defined in the Kotlin code, meaning you now describe your UI. Once people start seeing compose in action, it really becomes a delightful thing to program.
  • Jetpack compose makes it possible for Android views to coexist with compose UI in the same XML file. There are still paths that still need to be migrated to compose, such as the compose navigation. Once the app makes use of compose navigation, then the need for compose view will become redundant.
  • The search screen represents an entire screen. Composables rely on state in order for them to be composed or built on the screen or compose. These are events that are invoked by the caller of search area to decide what to do. Make sure that composables are as stateless as possible.
  • The search screen consists of smaller composables that all then help build up the whole screen as a whole. Try to make your composables as small as possible so that you can then reuse them to build a whole screen.
  • Up next, tv series manager is now available as an open source project. You can view the code I mentioned within this entire presentation and more. If you'd like to contribute to this open Source project, please feel free to do so.
  • Ahmed Tekua is a senior software engineer specializing in Android at Luno. He shows you how to create composables in your apps. Remember to take things step by step. The community is available for any questions or queries that you might have.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hi. In my talk from XML to compose, I'll be discussing my journey of transforming an existing large Android app to jetpack compose. I'm Ahmed Tikiwa, senior senior senior senior senior software engineer. Android am based in Cape Town, South Africa. The first question we'd ask is, why compose? Most Android development, including my own, include defining layouts using at least one XML file. This XML file will then contain a tree of UI widgets constituting an Android view hierarchy. For example, you'd have a constraint layout, and within that you'd have a text view and a button. If the user interacts with the screen, for example, this would result in a change in the app state, and thus a need for the UI hierarchy to be updated to display the current data representing the change state. However, to update the UI, a function such as find view by id is used to go through the hierarchy tree, and the internal state of the node, which is the UI widget, is updated through functions such as set text or add child. This manual change of the UI widget is very error prone, for example, trying to set a value to a node that has already been removed from the UI, thus resulting in an unintended exception. The more views that are part of the application, the higher the level of maintenance and complexity. Jetpack compose, on the other hand, was created with the intent to simplify the above and instead accelerate the way in which we develop our uis using less code and benefiting from a list of powerful tools all in Kotlin. In other words, no more XML defined layouts, which are referred to as the imperative approach. Rather, your views are now defined in the Kotlin code, referred to as the declarative approach, meaning you now describe your UI. So what are other companies saying about compose? At Monzo, it's much easier to trace through code when it's all written in the same language, which is Kotlin and often the same file. Rather than jumping back and forth between Kotlin and XML at Twitter, our theming layer is vastly more intuitive and legible. We've been able to accomplish within a single kotlin file what otherwise extended across multiple XML files that were responsible for attribute definitions and assignments via multiple layered theme overlays. So when was Jetpack Compose introduced? I first heard about Jetpack Compose and it was announced as a preview by Google at Google IO in 2019. And this is what Karen had to say at the time. One of the areas we never solved was UI we really wanted to look at. How could you make it super simple to develop UI and this is what Leland Richardson felt would happen. What I think is once people start seeing compose in action, it really becomes a delightful thing to program. Of course, as any developer, I was excited as I was already using Jetpack libraries extensively as part of my development, with now a shiny new library being added to accelerate or speed up my development with even less code. Though I was excited, I was also trying not to feel overwhelmed at the prospect of learning a whole new way of writing UI, as the imperative approach was what I was used to for many years. Also, due to time constraints, I also delayed learning jepky compose until recently. So how did I actually learn it? I started learning jepky compose through the course on the Android Developers website, which takes you through step by step into understanding the inner workings of compose from thinking in compose. The basics, navigation, theming, animation, integrating into existing apps, all of this through articles, videos and codelabs. The course also has a short quiz at the end to test your understanding of compose and includes a Jetpack compose badge which will be added to your developer profile on successful completion of the quiz. In addition to this, because I'm a visual learner and with a desire to thoroughly understand Jepper compose, I went through two paid Jetpack compose courses on Udemy. This approach is of course totally optional. You don't have to do this, but the extra visual tutorials help me. The first was a short course by Kathleen Gita, which is Jetpack compose crash course for Android with Kotlin, and the second and a more extensive course by Paolo Dichone, which covers the Kotlin fundamentals for those who either need a refresher or are new to Kotlin, and a series of different apps which are all part of this curriculum to help solidify the compose concepts and how all the components fit together coupled with state management. Once I was done with my learnings mentioned above, my desire was to now implement compose into my existing Android app. Up next tv series manager so I creators up next tv series manager in 2015, a passion project of mine and has been in production since then and available on Google Play Store for download. The app boasts the following screens a dashboard screen which shows a schedule of shows that aired the previous day, the current day, and the next day provided by the TV Maze API. A search screen allowing the user to search for shows with the results displayed in a clickable list of cards with data provided by the TV Maze API an explore screen showing the currently popular trending and most anticipated shows provided by the trackit API a show detail screen showing a summary of the show, its cost information, a ratings breakdown provided by trackit, as well as previous and next episode information provided by TV Maze. Then it also has a list of seasons which is a screen on its own for particular show, displayed in a list of clickable cards with data provided by the TV Maze API. Then there is a list of episodes for that particular season displayed as well in the list of clickable cards with data also provided by the tvmaze API. Then finally there is a trackit account screen which displays one of two screens, whether you are logged in or out, and if the user is logged in, their list of up next favorite it shows will be displayed. So adopting compose choosing the right approach what makes Jetpack compose so powerful is that it caters not only to developers creating new apps, but also to developers who have existing apps and would like to include compose into it. What makes the latter a possibility is the concept of interoperability. What this means is that jetpack compose code can live side by side with XML based code. With that said, adoption can be done in one of two ways, according to the Android Developers website. The first is called the bottom up approach, which starts by migrating smaller UI elements on the screen like a button or a text view, followed by its view group elements until everything is converted to composable functions. Then you have got the top down approach which starts by migrating the fragments or view containers like a frame layout, constraint layout, or recycler view, followed by the smaller UI elements on the screen. So the reason interoperability is emphasized is because Google understands that overhauling the application can be a very expensive exercise, and so introducing compose into an existing app should be done step by step over a period of time. Migrating an app to compose takes time, and that is the case for my app up next tv series manager, where there are still paths that still need to be migrated to compose, such as the compose navigation. Currently, I'm currently using Jetpack navigation to navigate between my screens, so I will be migrating as well. In the future to compose navigation. I will now show how I leverage the power of interoperability for every screen. Were my approach was a screen by screen approach were I converted each screen layout to compose. So what does interoperability look like? Each of my fragments has an XML layout view associated with it. Some screens also have an additional layout for recycler view items used by their respective adapters. As this is a gradual migration to compose where I'm going screen by screen, I will be completely removing fragment files in the future, so not right now, and have compose navigation, where navigation will be from composable screen to composable screen, as opposed to my current fragment to fragment navigation. Then compose view makes it possible to introduce compose UI into an existing XML layout. The compose view acts as a container to host the compose UI content, thus making it possible for Android views to coexist with compose UI in the same XML file. So in my view or views, rather I removed all the Android views, which are the text views, recycler views, nestor scroll view, constraint layout, linear progress indicator, and released them with compose view. Once the app makes use of compose navigation, then the need for compose view will become redundant. So this is how my layout looks. I removed all the other layouts and all I have is the compose view which acts as a container, and the compose or the compose UI content will be then injected into it by jetpack compose. Then this is how my fragment looks. So within there, I reference compose view with data binding. So as you can see there binding composecontainer apply. And that's how I'm referencing my compose view, which is in my layout. Then, according to the documentation, by default, whenever the view is detached from the window, compose disposes of the composition. Compose UI view types such as compose and abstract compose view use a view composition strategy that defines this behavior. So, to ensure unintended behavior, and to ensure that compositions are disposed of automatically when not needed. For example, when the screen is not in play, then you need to use set view composition strategy, which is required without adding this set view composition strategy, my app actually crashed. So within set view composition strategy, I'm defining the strategy as viewcompositionstrategy dispose on view tree lifecycle destroyed. That is the composition strategy that I'm defining. Then the set content, which is a composable, takes in a composable function as a parameter. In this case, it's the theme definition MDC theme, as you see there. So MDC theme is also another composable, and this is actually created by a library which I added into my up next tv series manager, which allows me to leverage the power of material design within my app that is not yet fully migrated to jetpack compose. So it allows you to basically use your existing theme. It basically reads all your existing theme elements and converts them into what Jetpack compose is then going to be using from a material design point of view. So it will create the theme KT file, which it needs, the color KT file, the type KT file, all these files that it needs in order for it to leverage the power of material design within jetpack compose. So all of this is done in the background, and then once I fully migrate, I can then create my own material design files and then I will no longer need the MDC theme adapter in order to do that. And so within the MDC theme, it also takes the compose and the composable in this case is search screen, which is a composable that I creators and I'm passing to MDC theme. So basically MDC theme takes in a composable and whatever composable is contained within it will now have the material design theme applied to it, which is very nice. And so within my search screen, I am passing in the nav controller. As I said, I'm using the current jetpack navigation, so I'm passing in my nav controller into it to allow my composable to be able to perform the navigation functions. So breaking down the changes, the screen changes starting with the search screen. Okay, so Jetpack compose is built around composable functions, and within these functions you can define your app's UI programmatically by describing the UI of your app, how it should look, and provide the data necessary to be displayed. You therefore no longer have to focus on the process of the UI's construction, which is initializing an element or attaching it to a parent and so on. You don't need to worry about that. So in my case, I'm using my focus on this particular screen. There is updating the input area so where the user will be typing in their search query as well as the search results list. So I'll be focusing on the search result item. So building a composable for a search result item and then displaying the list of composable items that I would have created. So that will be my focus on this particular screen. Then I'm going to be removing the recycler view adapter, the view holder, and the item layout. So basically getting rid of all of that and leveraging the power of compose to display this particular screen. So having an input area and having the list displayed there. So this is the composable that I then created for the search screen that you saw in the previous slide. So you have there a function called search screen, and I created this function to represent my entire screen and this screen there, or this function rather is annotated by at composable. And this is how you define your composables. This is how you tell compose that this particular function is actually a composable and it's going to represent certain things with regards to compose. So compose, as I said is built around composable functions. It allows you to define your app's UI, provide data to be displayed. No more focus on UI construction process. So back to the search screen. So you've got the function there which I created called search screen. Notice my naming convention. So because this composable represents an entire screen, I decided to name it to add screen there at the end and notice how it starts off with a capitalized search. This is the norm within compose to actually have capitalized function names for your compose. Then for dependency injection I use hilt in my app. And because my view models are hilt view models, I can then pass hilt view model as you see there to allow my view model to be provided to the compose. And then the hilt view model call is part of the hilt navigation compose dependency which I've added to my project. Then because my app uses live data from the view model, so the data in my view model is live data is being returned as live data. I can then transform that live data value into state using observer state. So one thing to bear in mind is that composables rely on state in order for them to be composed or built on the screen or compose. So the process of recomposition is xmlbased on state, so reacting to certain states. So in this case I've got two state variables which are the search results list as well as the is loading state variables. So every time there would be a new value posted into the live data, the return state will be updated, causing recomposition of every state value usage when I migrate up next navigation to compose navigation, I will then use a scaffold layout which automatically provides slots for the top bar, the bottom app bar. For now I'm using what is called a surface layout or a surface composable, which basically is just a material surface where you can add things on it. And in this case I'm then defining that this surface, I want it to occupy the entire screen using what is called a modifier. And these modifiers allow you to basically define properties such as padding or clickability. You can basically append these to your composables to be able to allow to customize them, look in a certain way or behave in a certain way. So modifiers are great when it comes to that. So when I migrate to jetpack to compose navigation, rather then I'm going to replace surface with what is called the scaffold composable. So the scaffold composable, like I said, will then allow me to have the top bar and the bottom app bar. And so right now, because I'm not using the compose toolbar or the compose bottom app bar, I'm then making use of surface rather and then migrating it once I've migrated to jetpack compose navigation. All right, so here you will see that within my surface I'm also defining a column, and the column is basically a composable that allows your views or your composables which are contained within it to be arranged vertically so they're from top to bottom. Right? Then within my column I'm also defining a composable called a box. So I basically want to add a linear progress indicator, but in order to ensure that the list does not jump or shift position on the screen, when the linear progress indicator is removed from the screen, I rather want to display the linear progress indicator on top of the list. That way when it disappears, the list remains in its position, it doesn't shift or jump. To achieve this, I use the box composable, which is the equivalent of a frame layout. In the imperative approach, the box composable allows views to be on top of each other. Then within the box composable I have my custom composable called search area. So this is a composable that I created as well as the linear progress indicator which is only displayed if the is loading state is set to true. Right then for this particular composable. Before I explain what is going on here, there is one important concept which is very critical to composables and this is the convert of state hoisting, which is the process of moving state all the way up to the caller in that way ensuring that composables are as stateless as possible. In my case, the caller is search screen and the composables below it need to hoist the state up to it as much as possible. There are times however, where state hoisting isn't always possible. However, it is best practice to make composables as stateless as possible. So as a general rule of thumb, state comes down, the composables and events go up. When composables are stateless it also makes them easier to reuse. So my search area composable will represent the text field for entering the search query as well as the search results. It accepts three parameters, the search results list which is a list of show search model and then two function arguments which are on text submit and on result click. These are events that the composable will respond to and hoist up. The responsibility to the caller of search area to decide what to do with that event. Ontech submit will be invoked when the user has entered the search query. This event is then hoisted up from search form composable. Then on result click is then an event. When the user clicks on one of the search result items, then this event is hoisted up from the search result list composable. Then here's what my search form composable that I created looks like. It has a mutable state variable called search query state. In order to ensure that this state survives the activity or process recreation using the saved instance state mechanism I use remember saveable. Now the user's query will be remembered in a state variable. My search form compose makes use of a search input field composable, which is a compose I created as a simple wrapper around the material outline text field which I will show in the next slide. The state variable search query state is then used in the input field to then used in the input field. Initially the value will be an empty value because that's how I initialize it there. So it's an empty string on the start. Then when the user types a value, the on value change event is invoked and then the search query state value is updated and remembered. So continuing on with breaking down the changes. So this is now in my search screen where I have my search input field. My search input field composable looks like this. As you can see it calls a material outline text field. Passing it the input label, the value state which is a mutable state variable of type string, and when the outline text fields onvalue change is invoked, then that event is passed ups containing the new value of type string. My search results list composable takes two parameters, the list to be displayed and a function argument which will be the onclick event containing the search result item. Search result list then makes use of the lazy column which is the equivalent of the recycler view but more powerful under the hood. It's very nice. Also with this no requirement for adapters, viewholders and so on. Lazy column and its other counterpart lazy row simply take a list and inside the lambda which is referred to as the lazy item scope, and then define which composable represents the column or row for that list. Similar to when you would create an XML layout for a viewholder. This time you create a composable that represents that column or row. So there the list and the on click the parameters that are being passed to it. All right, so in the previous slide where I showed the search screen composable, I didn't show the full implementation until I had covered the above. In order to make things clearer. First this is the full call for the search area composable where a list is passed to it and when the search area composable's on result click event is invoked. Search screen will then call the nav controller as part of the Jetpack Navigation library to navigate to the show details screen. Then when the on tick submit event is invoked by search area, search screen will then notify the view model that the ticks has been submitted, passing in the query itself. So here are the before and afters. So we'll start off with the search fragment. So with the search fragment. As you can see there on the left hand side I showed it in a previous slide where you've got that layout there, the input field and the results coming in at the bottom. And this is all purely xml, this is all designed within XML using a recycler view, an adapter view holder and so forth. Then on the right hand side I created a search screen composable and this is how it all comes out. So within my search screen composable, I've got search area which then takes in the search form composable and the search results list composable and this is how it's all laid out. As you can see, my search screen consists of smaller composables that all then help build up the whole screen as a whole, which is the norm or the convert or the best practice when it comes to creating composables. Try to make your composables as small as possible so that you can then reuse them to build a whole screen. Then on the dashboard fragment this is how it's all laid out with my three columns there or three rows rather. So you've got the shows that aired yesterday, today, and then below it will be the shows that are airing the next day. And all of this again was designed using XML. And then I created a dashboard screen composable. And dashboard screen composable consists of a shows row composable which I created. And my shows row compose uses the lazy row composable allowing my cards there to be displayed in a horizontally scrolling list. Whereas a lazy column allows you to scroll vertically, lazy row allows you to scroll horizontally. So I've got two composables. One composable there shows row which takes in a list and displays the list in a horizontal list format. And as you can see I'm using that same composable basically reusing the reuse concept. And I'm reusing it for the shows that aired yesterday, the shows that are airing today, and below it the shows that are airing tomorrow. Then on the explore fragment again also fully designed in XML, on the right hand side is the composable version of that where I created explore screen composable. And within explore screen composable I've got trending shows row composable which uses the lazy row to display my list horizontally. And then I've got popular shows row which also uses lazy row and then below it I've got most anticipated shows row which also uses lazy row to display the items. Then I've got show detail fragment on the left hand side again also fully developed using XML. And then this is the composable version of that. So there at the top there you've got the image which is a backdrop image. And then there's the title has well as the status of whether the show is running or not. So all of that is contained within the composable backdrop and title. And that is one contained compose. And then below it you find poster and metadata composable which I created for that little poster thumbnail as well as the metadata when it airs the genres and that attribution there. And then below it with the synopsis of the show. I'm just using the material design text composable to display my summary, then continuing on with the show details screen. So I've got a button there for seasons which allows the user to navigate to the seasons fragment. I've got the show cost and I've got the next episode and previous episode information below it. And this is the composable version of that where it's got show detailed buttons. So basically I created a composable that will have house those buttons there so I can add and remove buttons from that one composable with ease. And then I've got a composable called show cost list which will then display the cost using a lazy row, so scrolling horizontally. And then were got a composable called previous episode which has got the previous episode information, so previous episode title as well as the synopsis of that episode and the same thing for next episode which I call next episode which is a separate composable continuing on with the show details screen. Then you've got the previous episode information and then you've got ratings there at the bottom. So these ratings come from the trackit API for that particular show. And this is how I created it in compose. So I've got my composable which is previous episode composable and then below it I've got the track it rating summary compose which I created, which allows me to lay things out as you see there. So it's a combination of a text view, two text views as well has a linear progress indicator which allows me to create those ratings there that you see there with the ten and going a certain percentage. So I can basically define a certain percentage and display it like that. So this is a component that I created and now which I can just easily include there into my compose screen. Then this is the show seasons episodes fragment on the left hand side. And this is how it looks completely designed in xml. And this is its composable counterpart, so fully created using compose. I've got my section heading text which is a composable I created basically. Now all my heading ticks are customized or at least standard across the app. All I just do is just call section heading text. I pass the ticks I want to show and they all come out in the same way that I want them to. And then I've got show season episodes composable which uses lazy column to display those cards vertically from top to bottom. And each card is represented by show season episodes episode card composable. So this is a trackit account fragment screen which is displayed when the user is currently logged in. So they will see their favorite shows there as listed there. And this is the composable counterpart of that same screen. So you've got the composable section heading text and then below it the composable favorites list which uses lazy vertical grid which is another type of composable that allows you to display your items in a grid format, which is very nice. So I was able to achieve same layout on the left but using a composable called lazy vertical grid. And then each item within the lazy vertical grid is represented by list poster card composable which I created. So what still needs to be updated or convert? So the first thing is the toolbar, as you can see there. Up next, tv series manager at the top there that needs to be updated to be a compose version of toolbar. Then the bottom app bar also needs to be migrated and then just go back to the previous slide. So what I also need to update here is the navigation section of things. So I basically need to use compose navigation. I need to remove all the fragment files and I need to use scaffold instead of surface. Then I need to replace all my observer state calls with mutable state observation instead. Then I would like to add animations to up next as whole make things pretty, make things move smoothly and nicely. And also, that's also another aspect of compose. Compose makes animations completely simple, or at least simpler than the previous iteration with the imperative approach. And then I also want to add tests for my composables. Very important. And compose actually downs have this available where you can actually create tests for your composables. So in terms of resources, so there's the official compose documentation and this is where you'll find it. Developer Android Jetpack compose and then the official compose course, which I mentioned earlier, which I did, you can find it on developer Android compose, pathwayscompose, and then compose layout basics, which basically allows you to understand how things are laid out. If you want a deeper understanding of all of that, you can find that developer Android.com japakomposelayoutbasics and then the state existing, which I mentioned earlier, which is an important concept within compose. This is where you'll find it there then in terms of the code. Up next, tv series manager is now available as an open source project. Before it was closed source, but now I have made it an open source project. You can view the code I mentioned within this entire presentation and more. So basically to understand how I did things. And I follow the MVvM pattern and you can see how I basically set that all up from my repository using room, as well as having my remote data source and then having my view model, fetching that information and then passing that over to my composables. So all of that code you'll be able to see in more detail as I was not able to show that in greater detail due to the time constraints of this presentation. But you can feel free to check out the repository there in a branch called feature, adding all my changes there. I will make these live once I feel I'm satisfied with the overall look and feel of compose. And contributions are welcome from the community. So if you'd like to contribute to this open source project, please feel free to do so. Please just read the readme and the contribution guidelines for more information. And that is it. That is the end of my presentation again. I'm Ahmed Tekua. I'm a senior software engineer specializing in Android at Luno and you can find me on Twitter at Ahmeds. And it has been a pleasure showing you my migration, my journey of migrating, my up next tv series manager to jetpack Compose. And I hope that you will try it within your apps. Just know that take things step by step, which is really important, and sometimes it might feel like you are writing a lot of code in order to create a composable. But just remember that if you create your composables in such a way that you can reuse them, you won't have to rewrite most of your compose, you can actually just reuse them in another screen, which will actually make your development much quicker and much easier. And just know that the community is available for any questions or queries that you might have. You can also feel free to reach out to me on Twitter if you have any questions or concerns. And thank you so much for having me.
...

Ahmed Tikiwa

Senior Software Engineer - Android @ Luno

Ahmed Tikiwa's LinkedIn account Ahmed Tikiwa's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways