Your First Kotlin Android App: An App From Scratch

Aug 15 2023 · Kotlin 1.8.20, Android 13, Android Studio Flamingo | 2022.2.1

Part 1: Get Started with Android Development

07. Build the Game UI

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 06. Get Started with Jetpack Compose Next episode: 08. Modify Composables

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 07. Build the Game UI

When you make an Android app, you build the layout using Composable functions or Composables for short. Composables in Android are a collection of layout components which you use to build the app’s UI. You can also call them components or widgets.

Here’s what the BullsEye screen will look like at the end of this part but with all the Visible Composables highlighted and labelled. As you can see, it consists of different Composables that makes up the user interface. Do note that some of the Composables are invisible, and I’ll point them out to you soon.

A Composable is anything that gets drawn on the screen. In this screenshot, it seems that just about everything is a Composable: the text items, the buttons and the slider are all Composables. In fact, every user interface control is a Composable function.

Some Composables can act as containers for other Composables. The biggest Composable in the screenshot is one of these: it’s the widget representing the screen itself, and it contains all the other Composables on the screen. And this is no surprise because as its name suggests, a Composable function can be composed of other Composables and this goes on and on.

There are different types of Composables that ships with Jetpack Compose.

What makes each type different is a combination of what they look like and what they do. Here are the Composables that you’ll be using in this episode:

  • First, there’s Text. Text is a Composable that displays one or more lines of read-only text. The “Put the bullseye as close as you can to” message is a Text Composable, as are the various other labels in the app. Even the text “Hit Me” that goes inside the button is a Text Composable.

  • Second, there’s Slider. The Slider lets a user enter a value by sliding a control (which is called a thumb) along a straight-line track where one end represents a minimum value and the other represents a maximum value.

I want to point out that in most apps, you wouldn’t make the user enter a precise number value like this using a slider, because that can be kind of frustrating. However, for a game like Bullseye, the slider makes the game challenging, which is a good thing. After all, we don’t want to make it too easy for the player!

  • Third, there’s Button. Button is a Composable that performs an action when you tap on it. You can put any Composable you want inside a button - here, we’re putting a Text that says “Hit Me”.

  • Fourth, there are Layout components.

They come in different flavors like:

  • Column for vertical arrangement,

  • Row for horizontal arrangement, and

  • Box for depth.

Layout components act as containers for other Composables and they’re your basic layout tool in Jetpack Compose.

Unlike the Text and Button Composables, layout Composables are invisible by default.

The components a Layout composable contains are called its children, and a layout components’s job is to arrange its children along the specified axis.

So, a Column, arranges it’s child components on top of each other vertically.

You’ll be using a Column to arrange the four blue child views you can see here: the instruction label, the target label, the slider and its labels, and then the Hit Me Button.

A Row works just like a Column, it just arranges its children next to each other horizontally instead of vertically.

You’ll be using a Row to arrange the slider and its two labels in the order you see here.

Since Jetpack Compose creates user interfaces in code, this episode will give you a little preview of what it’s like to write code in Kotlin.

You might not understand every single line right away, or what all the strange syntax means - especially if you’ve never done computer programming or Kotlin development before.

Again! That’s Okay!!! All that’s important at this stage of the course is that you get the general idea of what’s going on. It’s all about learning via repetition!

If you followed along and created a project in the earlier episode then just continue from where you left off. If you didn’t that’s fine too.

Just download and open up the starter project for this episode by clicking on file in the menu bar. Then select “Open” and browse to the download location in the finder and open up the starter folder inside Android Studio.

I’ll close up the dialog since i got my project already open.

In case you get a build error talking about the SDK when you open up the project, no need to be worried. Just click on the link that tells you to install the required SDK and everything should be fine.

Now its time to start building the user interface for Bullseye. But before you do that, delete the Greeting composable since we’re building ours from scratch.

You’ll get two errors related to the Greeting function: one in the MainActivity and the other in the GreetingPreview. Not to worry, you’ll fix them soon.

The first composable you’ll create is the GameScreen composable. This would be composed of other UI components that will form the game screen.

Enter the following code:

@Composable
fun GameScreen() {

}

This is an empty composable as it contains no element. We know it is a UI elements because we added the @Composable annotation on top of the function.

Now, to fix the two errors we had, update the places where the Greeting composable was used to GameScreen like so:

Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
  GameScreen() // New Code
}
...
fun GameScreenPreview() { // Updated Code
  ...
    GameScreen() // New Code
  ...
}

With these updates, GameScreen is now the main content for the MainActivity. And the preview function is also updated to the name GameScreenPreview to match the naming of the composable it displays.

Now, I like putting my screen composables in a different file. So I’ll create a new file by heading over to the project panel. Right click on the reverse domain name of your project. Go to “New” then select Kotlin Class/File. Select File from the popup menu. Enter GameScreen as the name. Then hit the Return key.

Head back to the MainActivity file and cut the GameScreen and GameScreenPreview functions. Then paste them inside the GameScreen.kt file.

Open up the live preview window by clicking on the split tab. Then tap “Build & Refresh” to reload the preview.

Next we need an instruction text. Enter the following code inside the GameScreen composable:

Text("PUT THE BULLSEYE AS CLOSE AS YOU CAN TO")

And make sure to import the Text composable function by hitting Option + Return and select the one from the material3 package. And instantly, you can see that the preview window updates with the text. Go ahead and click on the zoom button in the preview window to make it fit the available space.

Next is the target text. You’ll actually make it a random number later in the course, but for now, set it to “89” like so:

Text("89")

Oops!!! What just happened? Why is the target text on top of the instruction text?

Well, by default, Compose stacks widgets on top of each other if you dont provide a way to arrange them.

We dont want to stack our composables on top of each other, so you’ll use a Column for this. Go ahead and wrap the texts with a Column like so:

...
Column {
  Text("PUT THE BULLSEYE AS CLOSE AS YOU CAN TO")
  Text("89")
}
...

And the preview updates to show the text arranged vertically but as you can see, the texts are aligned to the left. To correct that, you can pass in a argument to the Column like so:

Column(
  horizontalAlignment = Alignment.CenterHorizontally,
)
...

In here, you set the horizontal alignment to the center. Function arguments just affects the way a function behaves and in this case, we’re telling the Column to align its children to the center on the horizontal axis. Arguments are often called parameters depending on where they are used and they are always contained inside a parenthesis…more on that later.

Now, the target text should stand out from other texts since this is the target the players aims for. So let’s increase the font size to something bigger and make it bold. Enter the following code:

Text("89", fontSize = 32.sp, fontWeight = FontWeight.Bold)

You set the fontSize to 32.sp and sp is the text unit which stands for scaled pixels which will scale the text based on the font size that a user selects in their device’s system settings. Do note that we use the dot notation here instead of just writing it together with its unit. I just mentioned this because it is a different syntax if you’ve seen other programming frameworks.

You also made the font bold.

Now, make sure you have the sp value imported. To do this, hold down the Option and Return key to import the class or hover over it and click on import in the context menu that pops up. This adds an import statement to the top. If you scroll up and unfold the imports line, you’ll see all the imported code that this file depends on. This import gives us access to those files right inside our code. Once again, you need not worry about all these for now, we’ll talk more about import statements in the third part of this course.

Next, is the slider. Go ahead and add the following code inside the Column, right below the target text:

Slider(
  value = 0.5f,
  valueRange = 0.01f..1f,
  onValueChange = { }
)

You added a Slider and set its value to 0.5f. A Slider works with decimal based values and in this case, we’re using the float datatype which is a type of decimal based value. And it is denoted with a f after the number. So in this case, 0.5f would be the value at the 50% mark of the slider.

You set the valueRange from 0.01f to 1.0f which translates to 1 to a 100%. And finally, we have the onValueChange lambda which is an action that’ll be executed whenever you move the thumb of the slider. Right now it is an empty. You’ll handle this later on.

And not to worry about what lambdas mean at this point. Just see them as a block of code that’ll be executed at some point whenever they’re triggered. Kind of like functions without names.

Next, we need to add the labels for the slider which are two text. One containing the minimum value and the other the max value.

Since we want them to be positioned side by side with the slider, you’ll use a Row. Let me show you a quick way to do this.

  • First, click on the Slider.
  • Next to it you’ll see a bulb icon. Click on it.
  • Then select “Surround with widget”
  • Finally, select “Surround with Row”

This wraps the Slider with a Row. Nice and Neat :]

Then go ahead and add the two texts around the Slider like so:

Text("1")
Slider(
  ...
)
Text("100")

Then center the children on the vertical axis by adding the following argument to the Row:

Row(
  verticalAlignment = Alignment.CenterVertically,
)

I know you may be wondering where the maximum text went?…I mean the text with 100, right? Not to worry, we’ll talk about what’s going on in the next episode.

Finally we have the hit me button.

Add in the following code for this outside the Row but still inside the Column:

Button(onClick = { }) {
  Text("HIT ME")
}

The button contains a lambda which will be executed when the button it clicked. It is empty for now but we’ll implement it later on. Inside the Button, we have a text that says “HIT ME.”

You can see how the UI looks in the preview window but go ahead and run your code.

You can see it is displayed on the device but its not really how we want it to look.

Let’s handle that in the next episode.