Leave a rating/review
Notes: 08. Modify Composables
In the last episode you got started with working with composables and adding the base components for the Bullseye app. You were introduced to composable function parameters which lets you modify or affect your composables in some way.
For example:
-
you aligned contents of the
RowandColumn. -
you also increased the target text font size and made it bold using the
fontSizeandfontWeightparameters provided by theTextcomposable.
These are really cool and gives us access to customize our composables.
But what happens when you want to modify your composables further but the composable function doesnt have that feature you want?
For example, lets say you want to make the Text clickable or the set the height of the Column?
The Text composable doesn’t have an onClick parameter and the Column doesnt have a height parameter.
What do you do in those scenarios?
Well, the amazing folks behind Jetpack Compose provided us with Modifiers to help us with that :]
Modifiers as its name suggests just helps us modify our composables either by decorating or adding behaviors to them.
So you want your Text be clickable? Then add the clickable modifier to it.
Want to make your Column fill up the whole screen? Then add the fillMaxSize modifier to it.
It’s that simple.
Now you might be asking: Why create a separate interface for simple things like making a Text clickable or setting the size of a Column?
Why don’t we just add these features directly when creating the composable?
Well, this is not a good programming practice because:
You’ll be repeating code for the features in different composables.
For example if you add the clickable feature in the Text composable, you’ll write almost identical code if you also want to make a Column clickable.
So if you want to update the click code, you’ll have to update it in all your composables…and you’ll agree with me that’s not a fun experience.
Another reason you wouldn’t want to do that is because you want a composable to do only one thing.
-
A
Textshould only display text and have only text related parameters. -
A
Columnshould only arrange its children vertically and have only arrangement and alignment related parameters -
Same thing for a
Buttonand other composables.
And this follows the Single-responsiblity principle of the SOLID principle developed by Robert C. Martin popularly known as Uncle Bob. So a class or a function in our case should have only one responsibilty and if you ever want to change a composable, then it should be only related to its core responsiblity.
So if you were were updating the code for the Text composable, you should be updating only code related to text and you
shouldn’t be worrying about things like clickability and so on.
In turn, this makes your code more focused, scalable and maintainable.
It’s cool to jump and start coding stuffs but its also super important you know why you’re doing what you’re doing and it won’t hurt to start following best practices from early on in your Android development journey.
So in a nutshell, Modifiers just gives you access to modify your composables further. Alright, let’s get back to code.
First, when you run the app, everything is pushed to the top of the screen.
Let’s use a modifier to set the size of the Column.
Add in the modifier like so:
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize() // New Code
)
And instantly you can see the Column fills the whole screen in the preview window.
But when we run the app, it still looks the same in the emulator.
Before we fix that, lets use another modifier to add some spacing between the edges of the screen and the children of the Column.
For this, we use a padding.
Add it in like so:
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
In here, you added a padding of of 16.dp and you can instantly see some spacing between the contents of the Column and the screen.
The dp unit stands for density-independent pixel and it is the recommended unit for UI layouts.
It differs from sp as the user’s device font size setting does not affect it.
Now, we need push the items to the center vertically.
For this, add in the verticalArrangement like so:
verticalArrangement = Arrangement.Center,
Then run the app.
This makes the Column place its children on the center.
And do note that it is arrangement and not alignment when referring to the main axis.
So since its a Column and a Column arranges its children vertically, you refer to how you place its children on that axis as arrangement.
The preview and emulator shows the items placed at the center but we need some spacing between them.
We can easily change the verticalArrangement of the Column to SpaceBetween or SpaceEvenly like so:
verticalArrangement = Arrangement.SpaceEvenly,
But doing this here does not give us the power to control the space the game components take. I’ll undo that now.
For this you’ll use the weight modifier and the Spacer layout widget.
You need to wrap the children of the Column into another Column.
Go ahead and select them.
Click on the bulb icon.
Select “Surround with widget.”
Then choose “Surround with Column.”
This places the the UI components inside a new Column.
Next, add in the alignment and arrangement arguments to the inner Column but this time use SpaceEvenly for the verticalArrangement like so:
// Inner Column
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly,
Finally use the weight modifier by adding in the following code inside the parent Column:
Spacer(modifier = Modifier.weight(.5f)) // New Code
Column(
// ...
modifier = Modifier.weight(9f) // New Code
) {
//...
Spacer(modifier = Modifier.weight(.5f)) // New Code
Here, you added Spacer composables which is just used to add an empty Space in Jetpack Compose.
We then give it a weight density of 0.5f which can just be written as .5f.
Remember f here stands for floating point number as the weight function accepts a decimal based number in this case a float.
Now the value we pass here is relative to other children of the Column.
So we have another Spacer at the end which also has a weight of 0.5.
Finally, the inner Column is given a weight of 9f.
Take a moment to see if you could guess what this means…
Alright, what this simply does is to divide the space between the three children of the parent Column.
So, the first Spacer takes 5% of the the available space.
The second Spacer also takes 5% too.
While the inner Column which contains our game components takes the remaining 90% of the screen.
And you can see that in the live preview and also when you run your app.
We’ll fix the spacing between the instruction and target text later on.
Cool!!!
Now, to round off this episode, lets fix the max value label that doesnt show up in the Row.
I’ll use weights to fix this up but you can pause the video and try it out on your own if you’re feeling adventurous :]
Alright, before I solve this problem let me explain why you dont get to see the max value label.
The reason is simple.
A Slider is built to fill up the maximum available width.
And if you think of it, this makes sense because most sliders are always wide enough to fill up the horizontal space in user interfaces.
Giving it a fixed width wouldn’t make sense as you dont know how developers would use it in their user interfaces.
And that is why you can’t see the max label because the Slider has consumed all the remaining horizontal space in the Row.
Okay, let’s fix this up.
Update the code for the Row to the following:
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"1",
textAlign = TextAlign.Center, // New Code
modifier = Modifier.padding(start = 16.dp) // New Code
)
Slider(
// ...
modifier = Modifier.weight(1f) // New Code
)
Text(
"100",
textAlign = TextAlign.Center, // New Code
modifier = Modifier.padding(end = 16.dp) // New Code
)
}
You set the weight of the Slider to 1f.
This tells the Row to divide the horizontal space remaining after measuring unweighted children elements in this case, the two Text widgets.
It then distributes the remaining space left according to this weight and since we gave the Slider a weight of 1f, it takes up the whole of the remaining space.
If that explanation was too technical for you, what simply happens here is that you made the Slider take up the remaining space after making sure the two Text widgets have taken up the space needed to display them.
Not to worry, all these will make more sense to you as you continue building user interfaces in your Android development journey.
Next, you aligned both text to the center using the textAlign property.
After that you added a padding modifier to add some space at the start for the minimum label and at the end of the maximum label.
This helps us push the texts away from the edges of the screen.
Alright, run your app once again.
And the slider labels are displayed correctly.