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.