How to build UI with Android’s Jetpack Compose (Introduction and Basics)
Articles Blog

How to build UI with Android’s Jetpack Compose (Introduction and Basics)

February 25, 2020


Alright everybody, what is going on, this
is the Odd Programmer and today we are going to discuss “Compose”. So let’s get started. “Jetpack Compose” is a toolkit for building
Android UI. It combines a reactive programming model with
the ease of use of the Kotlin language. “Jetpack compose” is based upon a declarative
UI pattern. Which means that we describe our UI by calling
some functions on the go. Normally in Android, we declare our UI in
XML which is then converted to Java code by Android’s framework but our applications
are usually reactive which means that based on data, some components of our UI changes. So it means that first, we are creating the
view and then mutating it via getters and setters. This implies that the views have a state of
their own and every time we update the view, we are changing its state. In the case of Compose, the problem of views
having their own state is eliminated. So what is Jetpack Compose? Compose is a fully compatible Jetpack widget
built- entirely in Kotlin. They are not Views, in fact, they are more
basic and smaller modules that can be used to build the skeleton of our UI. Compose works by defining our UI using declarative
functions which are annotated with “@Composable”. So we define a UI tree by these @Composable
functions and the rest is handled by the Compose compiler. Now keep in mind that Compose is still in
pre-alpha stage and API surfaces are likely to change so it should not be used in production. So enough with the theory now let’s practice
Jetpack Compose. In this tutorial, we will make the ”ChampionGram”
application. As you can see here, we have a scrolling list
of superheroes and villains. On top, there is a horizontal scrolling list
of “Stories”. When we tap on it, the posted photo is shown,
we head back by tapping on this button and when we tap on the superhero post, a new screen
opens up with details of that superhero or villain. We have the option to mark our favorite champion
and there is a share button that will share the Wikipedia link of that champion. Of course, all of these are just UI, there
is no persistence data storage, no networking, the data shown is hardcoded. It is just bare bone UI, and our main focus
here is Compose. So before we dive into the coding part make
sure you are using the Canary version of Android Studio because it is the only version that
supports Compose, the link will be given in description. Second thing is to make sure to clone the
repository given in the description to quickly set up the project. Now let’s get started. So first off all take a look at the build.gradle
file of the app module. As you can see here all the dependencies related
to compose are added. Second thing is to enable the “compose”
buildFeatures. Now as you can see in the project structure,
there are two directories already added. The first one is data that contains data kotlin
file, second is the model which contains Champion kotlin file. So first take a look at our Champion model
file. As you can see, it contains a “Champion”
data class with some properties. After that, there is another data class Story,
which is annotated with “@Model”. This allows this data class to be observable. This means that any composable function which
will be using this Story data class will automatically get updated whenever there is any change in
the data. The whole UI hierarchy doesn’t get recreated
but only those components which use the model data get recreated with updated values. At last, we have this enum class with two
values. We will use this class later in this tutorial. Next, take a look at this data kotlin file. This file contains some predefined instances
of our data class which will be used by the UI, anyway, it is not much of concern feel
free to explore it yourself. Now open the MainActivity file and take a
look. The first big thing is this Greeting function. As you can see it is annotated with @Composable. Functions that are annotated with @Composable
can only be called from the scope of other Composable functions. Okay, so the Greeting function takes a string
parameter and calls this “Text” function. Text is a Composable function provided by
the Compose library. This function is used to declare a Text element
which is equivalent to the TextView in XML. So we are passing in our string to the Text
function. Next, we have this DefaultPreview function
which is annotated with @Composable as well as @Preview. This annotation allows the function to be
previewable alongside the code. Just head over to this split tab or design
tab to see it. You might have to refresh the build to see
it. One thing worth mentioning here is that functions
annotated with @Preview can not have any parameters. Next, we are calling this Greeting function
inside our MainActivity’s onCreate function. We usually do a setContentView but instead,
we are calling this setContent inline function which accepts a child composable and sets
to the root view of the Activity. This MaterialTheme is also a Composable function
that has Material design specifications of typography, colors etcetera. Now you can go ahead Run the application and
you will only see “Hello Android” in your activity. Now we will create the UI for our ChampionGram
application using compose. So ChampionGram will have three main screens,
the first one is the home screen which will have both profiles as well as stories in scrolling
lists. Second is the story screen and third is the
profile screen. … So first right click and create a new directory
and name it “ui”, as you can see I’ve done here. Next, create a kotlin file and name it ChampionGramApp
as you can see here. This will act as the main container of our
app. We will come back to this in a while. Next right click and create a kotlin file
and name it “Navigation”, this class will be responsible for in-app navigation. Third, create a kotlin file and name it “Themes”. This file will contain the global theme of
our application. Now open “Themes” and create a ColorPalette
object. The ColorPalette class is used to define the
complete color definition for the theme. As you can see I already have them defined
here, such as primary which is the primary color of our application. You may already be familiar with all these
color terminologies. Next, we have “primaryVariant” which is
the “primaryDark” color from XML themes, which is used for the status bar. Next, we have “onPrimary” which is used
for the items which will be drawn above the primary color, like texts etcetera. Next is “secondary” color which stands
for the accent color, the color which is used for floating action buttons etcetera. Next is “onSecondary” which is the color
for objects above “secondary” color. Next is “background” color, which is used
for default background color, “onBackground” which is self-explanatory. Next is “surface” color which is used
for colors of surfaces for cards etcetera. After that “onSurface” which is again
self-explanatory. The second last is “error” color which
will be used for any error and last is “onError” which is again self-explanatory. So that’s all for the Themes file if your
app needs to have a dark theme, that can be defined here as well. Next, open the “Navigation” file. First, create a sealed class “Screen”. A sealed class is like enum class but a subclass
of a sealed class can have multiple instances that can contain state. The first element of the sealed class is an
object “Home”. The second subclass is a data class “Profile”
with a property “championId”. The third subclass is another data class “Story”
with a property “storyId”. This sealed class will be used for state navigation
of the application. After that, we have a Model object which represents
the current screen of the application. As you can see it is annotated with @Model,
which means any changes made here will automatically reflect back in the UI. So anyway, we have a var inside the Navigation
object which holds the current screen. The default value for it is the “Screen.Home”. Finally, we have a function that will be used
to navigate between screen states. As you can see we take a destination screen
as a parameter and set the current screen to it. So that’s it for the Navigation file. Next, we look at the “ChampionGramApp”,
which is the main container of our application. First, create a Composable function “MainScreen”. As the name suggests this will act as the
main screen of our application. So the first element is this “Crossfade”. This composable function is used to transform
between two layouts with a crossfade animation. It takes two parameters, first one is the
“current” and the second is the child. For the “current” we are passing the default
current screen which is the home screen. For the child, we are passing in a lambda
which first checks the screen’s state and navigate accordingly. So when the screen is Screen.Home we show
the HomeScreen. Next, we will have screens for the story and
profile. We will do this later in this tutorial after
creating respective screens. Another composable function here is this main
ChampionGramApp function. The first element of this function is the
MaterialTheme composable. To which we are passing in our custom ColorPalette
which we created earlier. Next, we are calling the MainScreen composable
which we created now, as it’s child. So that’s it for this file, we will come
back to this later for further navigation-related adjustments. Next right click under “ui” directory
and create another directory called “home”. This directory will contain all the files
for the home screen of our application. So let’s create some files one by one as
you can see here. First, create a file “HomeScreen” then
“HomeStory” and last “HomeChampionCard”. Open the “HomeStory” file. This file will contain the composable function
for the story items which will be shown at the top of the home screen in a horizontal
scrolling list. This is what we want to create. So create a composable function and name it
“HomeStory”. It takes a story object as a parameter. The first thing we are doing is to get the
image instance that is associated with the story. So we have this image resource to which we
are passing in the image id we want to use. For now, let’s say that this plus unary
operator is used to resolve the resource we want. Next, we are creating a chain of composable
functions to get our desired UI. First is this Ripple function, which creates
a ripple effect for its child elements. Under Ripple, we have Clickable composable,
which makes it child element clickable. The clickable function is taking two lambdas,
the first one is onClick, which will be called when this UI element is tapped or clicked. We want to navigate to the respective story
screen and change the status of this story to “Seen” when it is tapped. So we will look at the navigation part later,
for now, let’s just change the status to “Seen”. So we have this Story.status=StoryStatus.SEEN. A second lambda that this Clickable composable
takes, is the child. We are using Container composable. As the name suggests this composable acts
as a container for its child elements. The container provides many layout properties
for its child, like padding, size, alignment and size constraints. In our case, we are setting the size of the
container to be 96dp in width and 116dp in height. Then we are putting a column composable, this
will act as a vertical LinearLayout, we are passing in the Expanded modifier which will
make this column occupy the size of its parent. It is similar to the “match_parent” attribute
in XML. Next inside the column first element is another
container that will contain the thumbnail image. We set the size of this container to be 96x96dp. Then we have this Clip composable, which is
used to clip its child element according to a shape provided. In our case we want the thumbnail to be in
a circle shape so we set the shape to CircleShape. And finally, we are calling the DrawImage
composable, which is used to draw images, similar to ImageView. Now we are checking the status of the story,
if it is not seen or new then we show a colored border around the thumbnail image, else it
is not shown. We use DrawBorder composable for that purpose. It is taking two properties, first is the
shape since our thumbnail is in circular shape we also set the border shape to be the same. Second, we are passing a border object with
color and size. So the thumbnail part is done. Next, we will have the champion name displayed
below the story thumbnail, so we create an alignment composable which aligns its child
element to its parent element according to the alignment provided. In our case we want the name to be shown at
the bottom center of the container so we have the alignment set to “BottomCenter”. Inside the alignment composable first we are
checking if the status is seen or not. If it is not seen then the text is shown in
black color with full opacity, else the opacity is lowered to make it look a little bit dull. We are using Text composable for that purpose. As you can see it has many properties. The first one is a text which is displayed,
second is style property. We are inheriting style from the predefined
material theme. The third is maxLines property which is used
to set the maximum lines of text to be displayed if the text is large enough. You may be familiar with this one if you have
worked with TextView in XML previously. Last is overflow property which is used to
define how the text-overflow is shown, in our case we show it by an Ellipsis or three
dots. As you can see here we are lowering the opacity
if the story status is not new, to make it look dull which will give the impression of
the story to be seen already. So that’s it for this HomeStory file we have
this preview composable which will show how it looks, if I switch over to the split tab
we will see it. Next, open this HomeChampionCard file. We will create something like this. It has a picture, then two texts all contained
in a card. So, first of all, create a composable function
and name “HomeChampionCard”. This function takes in a champion object as
a parameter. We are getting an instance of the champion
image via imageResource the same as we did with the story thumbnail image. Next, we are calling a Card composable function. This composable is equivalent to the CardView
from XML. We are passing in some properties like shape
which is set to RoundedCorner and a Spacing modifier. This modifier will add the space on all sides
of this card layout. These spaces will be external so we can say
that it acts as the “layout_margin” from the XML. Next, we have this Ripple composable which
you’ve already seen in the previous file. Here we have set the bounded property of the
Ripple composable to true. This makes the ripple to originate from the
touch location and clip to the shape of the layout. While unbounded ripple originates from the
center of the layout and not the touch location. Then we have Clickable composable which you
are already familiar with. Then we have Column and inside Column we have
Container, you already know what these two composable do. One thing to mention here is this “wraps”
keyword. Wraps can be used to chain modifiers, the
left one always has higher precedence than the right one. So in our case, we set the width to 0 dp but
we are calling the ExpandedWidth function which stretches the target layout to occupy
the full parent width. Since ExpandedWidth wraps Size so, ExpandedWidth
has higher precedence. Inside this container, we are calling DrawImage. Then we are calling another column to vertically
align those two texts. Again we are using this Spacing modifier but
this time we are explicitly mentioning the sides for spacing, if not mentioned the spacing
happens on all four sides. Then we are calling text composable, the first
one has the champion name, the second one has the champion’s made up name. So that’s pretty much it for this composable. Down here we have the preview. This is how it will look. Now it is time to create the Home screen composable. So open the HomeScreen file. This is what we want the home screen to look
like. It has a Top app bar and below it is a horizontal
scrolling list of stories and below that it is a vertical scrolling list of champions. So first create a composable function and
name it “StorySection”, this will be responsible for showing the list of stories in a horizontal
scrolling list. The first thing is this HorizontalScroller
composable which allows its children to be scrolled horizontally. Inside HorizontalScroller we have a Row composable. A row is like a LinearLayout with orientation
set to horizontal. For each story first, we are creating a WidthSpacer
which creates an empty space with specified dp. Then we are calling HomeStory composable which
we created earlier and pass in the story. Next, create another composable function and
name it “ChampionSection” which takes a list of champions as parameter. For every champion, we create a HomeChampionCard. At last, we have this small helper composable
which will be used to create a divider element. Finally, we create the HomeScreen composable. First, we create a Column to align each item
vertically. The first item is TopAppBar, and for the title,
we are aligning it to the center and then call the text. Then we are calling a VerticalScroller composable
which makes it child elements to be vertically scrolled. We are passing in the Flexible modifier. Flexible modifier works the same way as “layout_weight”
attribute works in XML. Flexible takes a flex value according to which
it expands the target layout. While creating the UI first Inflexible elements
are calculated then Flexible elements are calculated. Inflexibles have a fixed size while Flexibles
can expand or shrink. Then we are vertically aligning the StorySection,
HomeDivider, and ChampionSection one by one. The stories and champions list we are passing
here is the same from the Data file which we already have. So that’s it for the Home screen, this is
how it will look. We can go ahead and run the application. We now have a complete working home screen
but when we tap or click nothing happens. So our next step is to create respective screens
for story and champion profile and a way to navigate between them. To catch up till this part of the tutorial
you can check out the “home” branch from the Github repository given in the description. …
Okay so next we will create the screen for respective stories. So when we tap on a story from the home screen,
their respective story screen will show. Here’s a quick look at how it will work
and look like. We have a button on the top to navigate back. Then we have the champion name at the top
center. The whole layout is occupied by the story
image and finally, at the bottom there is a text which says “Posted by” respective
champion name. So let’s get started. First right click under “ui” directory
and create a new directory and name it “story”. Again right-click under the story directory
and create two new kotlin files. The first one is StoryScreen and the second
is StoryContent. Now first open up the StoryContent file and
create a new function “StoryContent”. This composable will be responsible for showing
the story image and the bottom text. So, first of all, we are getting the image
instance from the id. Then we are using the DrawImage composable
to draw the image. Then we have Alignment composable to align
the text to the bottom center. As you can see here we are using the predefined
material style for the text but there’s this copy function. So what is it? The copy function can be used to override
some values from the predefined material style, in our case we are overriding the color to
white. So that’s it for this file. Next, we need to edit the StoryScreen file
and create the top section of the story screen which shows the back button and the name but
before we do that let’s create a new file under “ui” directory and name it “vectors”
as you can see I have here. We will create some composable which will
be needed by the StoryScreen and you will see that in a minute. So open Vectors.kt and create a composable
function and name it “VecotrImage”. It will take three parameters. First is a modifier whose default value is
none, second is id which will take the integer id of a vector drawable and third is tint
which takes a color value whose default value is transparent. So, first of all, we are getting the vector
resource. Then we have this “WithDensity” component,
this function is used to convert dimensions between each other. So here it will be used to convert dp to pixels. Then we have a container to contain the vector
image. As you can see we have our input modifier
wrap around the default size of the vector so in this way we can override any modifier
if we want to. Finally, we are calling DrawVector to draw
the vector image. Next what we will do is to use this vector
image as a clickable button. So create a new composable function and name
it “VectorImageButton”. As input parameters, we have a modifier, vector
id, tint color, and finally a lambda function which will be called upon onClick event. So, as usual, we first have a Ripple composable
to create a ripple effect when tapped, then we have a clickable function to actually make
the image clickable, then finally we are calling our newly created composable function “VectorImage”
which will show the vector image of the provided id. We are passing in the modifier, vector id,
and tint color. So we have our vector image ready. Now time to get back to the StoryScreen. So open the StoryScreen file and create a
new composable function and name it “StoryScreen”. This takes in an input parameter, story id,
this id will be used to locate the respective story from our predefined list of stories. So that is the first thing we are doing. We are getting the story from the list of
stories. Then we are calling the StoryContent composable
which will show the story image as well as the bottom text. The only things left are the top back button
and the champion name at the top. So next thing we have is an Alignment component
that is set to align the child to top left. Whose child is the VectorImageButton that
we created a while ago. As modifier we pass in the spacing from all
four sides, then for the vector id, we pass in the id of the back button which is already
included in the project. For tint we are passing the color white and
lastly, for onClick we are passing the “navigateTo” function which we have created earlier in
this tutorial to navigate back to the home screen. One thing worth mentioning here is that compose
does not have any solid navigation system and what we are doing is a temporary workaround
so when we tap the navigation back button we will exit the app because our implementation
of changing screens does not involve any stack mechanism to store the previous screen. In fact, we are not actually changing the
screen at all. We’re just changing what is currently displayed. So, for now, to navigate we will have to use
this vector image button at the top. Okay next we have another alignment component
this time it is set to top center, this will be used to display the champion’s made up
name as you can see here. So that’s all for the story screen. Next, we get back to the ChampionGramApp file
and add a condition for the navigation of the story screen as you can see I have done
here. Next, open the “HomeStory” file under
“home” directory and add this navigateTo function here. So that we can navigate to the respective
Story screen by clicking on the story thumbnail from home screen. When you run the application you will get
something like this. After we tap on a story we are shown the story. We can tap on the back button to get on the
home screen. So we have added the story screen next and
the last thing to do is to add a screen for the champion profile which will be shown when
we tap on the champion card from the home screen. To catch up to this part of the tutorial,
check out the “story” branch from the Github repository given in the description. …
Okay, now time to create the profile screen for our champions, so right-click under “ui”
directory and create a new directory and name it “champion”. Now right click under champion directory and
create two new files, “ChampionContent” and “ChampionScreen”. Same as we did for the story screen. Here’s a look at what we will build, this
is the champion profile screen. First, there is a top app bar followed by
an image of the champion then some texts and at last, there is this bottom bar which has
two buttons for favorite marking and sharing. So, first of all, open the ChampionContent
file and create a composable function and name it “ChampionContent”, leave this
as it is. Create another composable function and name
it “HeaderImage” which takes a champion object as a parameter as you can see here. This function will be used to show the image
of the champion. Inside this function first, we are getting
the image instance from the id. Then we are creating a container for the image,
width is set to ExpandedWidth which will occupy its parent, and minimum height is set to 280
dp. Below this we have three more composable functions,
first is MadeupName, second is HeaderTextKey and third is HeaderTextValue. All these three functions are used to display
texts in different styles. As you can see here, these are already explained
so I’m skipping on these. At last we have this “BulletText” function
which will be used to display the texts in bullet points. As you can see this takes a list of strings
and traverses each string one by one and shows it in a formatted way. Now scroll back up and edit this ChampionContent
function. As you can see it takes two parameters. The first one is the modifier and the second
one is the champion. Now the first component is a VerticalScroller
because the contents might be out of bounds vertically so we need it to be scrollable
to view the whole content. It has a column to vertically align the children. The first child is HeaderImage, then we have
another column to vertically align the texts. So inside the child column first we have a
HeightSpacer to insert a vertical space. Then we have MadeupName composable to display
the champions made up name, the same is done with other texts as you can see here. We have Row to place the key-value texts horizontally. So we are displaying these things in the sequence,
first is champions real name, second is their place of origin and last is their list of
abilities which is shown in bullet points. So our ChampionContent composable is completed. Next, we will edit our ChampionScreen but
before we get to that let’s create a new kotlin file under “ui” directory and name
it “Bookmark”. If you remember our champion screen has a
favorite button at the bottom and this Bookmark will allow us to create such buttons. So open the Bookmark file and first create
a composable function and name it “BookmarkButton”. It will take three parameters, first is a
boolean which will represent the current state of the button. Second is a lambda which will be called whenever
the bookmark button is tapped, third is the tint color for the button. So the first component is Ripple, its child
is this “Toggleable” function which acts as a switch. It takes two parameters first is boolean which
represents the on-off state of the toggle. Second is a lambda which is the callback function
invoked whenever the toggle state changes. Inside this toggleable, we have a container
of size 48x48dp. Inside it, we are checking if the toggle is
on or not according to which we are setting the vector image. If the bookmark is not on then we have an
outlined vector and if it is on then we have a filled vector. Next create this model here, which will store
the bookmark state. Inside this model object, we have a ModelList
of Int which will store the champion id of the bookmarked champion. ModelList is an observable version of the
list. Next, we have this function here, which takes
in a champion id and checks if the champion is already bookmarked or not. If it is then it removes from the list else
it adds to the list. Last we have this “isFavorite” function
which checks if the champion is bookmarked or not and returns a boolean value. We will use this function later. Now open up the ChampionScreen file. This will contain all the composable required
to draw the champion profile screen. So go ahead and create a composable function
of the name “ChampionScreen”. Now if you remember our champion screen will
first have the top app bar then the champion content which we already created and at the
bottom a bottom actions bar which will have two buttons. One for bookmarking and another for sharing. So first create the bottom actions bar. So leave this champion screen composable for
now and create another composable below it and name it “BottomActionsBar” as I have
here. This will take a champion object as a parameter. First of all, we are getting the context,
it is required for the share button to work. Next, we have a surface component with an
elevation of 2 dp. Then we have a container with a height of
56 dp which wraps over the expanded modifier which will allow the container to be stretched
width-wise. Then we have the Row which places items horizontally. Then we are calling our bookmark button composable. For its parameters, we pass the current state
of bookmark for the champion, then we are passing in the function to toggle bookmark
when it is tapped and last we are passing in the tint color for the bookmark button. In our case it is red. So the bookmark button is done, next for the
share button we will create another composable function. So go ahead and create another function and
name it “BottomActionButton”. It takes three parameters. First is an id for the vector drawable, second
is the tint color and third is the lambda function which will get executed on click
event. So for the UI, we have ripple, clickable,
container which you all are familiar with and finally, the DrawVector which we created
earlier. Next, we create a function that will create
the intent for sharing. So we have this here, these are self-explanatory
and you guys may already be familiar with. Okay now we get back to the Bottom actions
bar and after the bookmark button, we insert our bottom action button which will be used
for sharing. Finally, we get back to the champion screen
and insert the bottom actions bar at the bottom. So that’s all for the champions screen it
should look like this now. Now we need to add the navigation mechanism. So head back to ChampionGramApp file and add
another condition here, if the screen state is changed to profile then we are calling
our champion screen composable with the respective champion id. Next, open the “HomeChampionCard” file
under the “home” directory and add this navigateTo function here. So that we can navigate to the respective
Profile screen by clicking on the champion card from home screen. So that’s all for this tutorial you have
now created an application via compose library. You can go ahead and run the application. To catch up till this part of the tutorial
you can check out the “profile” branch of the Github repository given in the description. So that’s all for this tutorial, I hope
you learned something good today. Good luck! And don’t forget to like subscribe and do
all that good stuff. This is the odd programmer peace out.

Leave a Reply

Your email address will not be published. Required fields are marked *