In case you haven’t read the Compose introduction yet, I suggest you read this article

As promised, here I am with a second article about Jetpack Compose. We now start to get a little more technical by seeing the first components made available by the framework.

At the base of every single app we have the Labels and the Buttons, the simplest components that can be used to compose the UI of our app.

Let’s start with the Label.

Who among you has never found yourself having to add a TextView to an xml to display a label? The steps, on the agenda for millions of developers worldwide, would be: Inserting a TextView in the xml, associate it with an id, retrieve the associated view via findViewById and, if necessary, store it in our Fragment/Activity/Custom View. Also remembering the need to inflate the layout. Now, though, you can say goodbye to all of that and add a label in two or three lines of code.

First of all, a premise must be made: Each graphic component you want to create or use must be “contained” in a method annotated with @Composable for example

@Composable
fun MyFirstComposeLabel() {
    //Your code here
}

By doing so, the compiler and the Linter are able to recognize the Compose workspace by suggesting the usable methods. As will also be suggested by the Linter, the name of a method annotated with @Composable must have the first letter capitalized while the name of a method, which however returns any object (a view, a string, etc), must have the first lowercase letter for example:

@Composable
fun myFirstAnnotatedString(): AnnotatedString {
    //Your code here
}

Returning to our labels; Let’s create a method that will be used to render our first label

@Composable
fun MyFirstComposeLabel() {
    //Your code here
}

and inside it we are going to add the component called Text equivalent to the old TextView

@Composable
fun MyFirstComposeLabel() {
    Text()
}

At this point Android Studio will suggest different ways of using this component such as using it through a classic String or an AnnotatedString, with which you have a high level of customization (font size, color, etc.) for the entire text or for only a few portions.

We start by using a normal String and by declaration we use the text parameter assigning it the desired string

@Composable
fun MyFirstComposeLabel() {
    Text(
        text = "Hello World"
    )
}

In case the string has been inserted in a resource file, it is possible to use the stringResource method provided by the framework

@Composable
fun MyFirstComposeLabel() {
    Text(text = stringResource(R.string.name_of_desired_string))
}

As for the AnnotatedString the code gets complicated by a few lines to write.

As I mentioned earlier, the AnnotatedString are the equivalent of the old SpannableString with which you can get a high level of customization of the strings: text portions in bold, clickable portions and much more.

So let’s create our usual method and call the builder for an AnnotatedString using the buildAnnotatedString method.

@Composable
fun MyFirstComposeLabel() {
    val myAnnotatedString = buildAnnotatedString {
    }
    Text(text = myAnnotatedString)
}

This way, within our builder, we will have the possibility to use methods like append, withStyle and withAnnotation.

In this example we want to highlight the portion of text consisting of “privacy policy” in the sentence “By registering you accept the conditions described in our corporate privacy policy” using the append and withStyle methods.

Strings are not retrieved from resources for simplicity, but even when creating an AnnotatedString you can get your resource using the stringResource method.
The result will be:

@Composable
fun MyFirstComposeLabel() {
    val myAnnotatedString = buildAnnotatedString {
        append(“By registering you accept the conditions described in our corporate ”)
        append(“ “) //Let’s add a blank space between our strings added via `append`
        withStyle(
            style = SpanStyle(fontWeight = FontWeight.Bold)
        ) {
            append(“privacy policy”)
        }
    }
    Text(text = myAnnotatedString)
}

which printed on the screen will be: By registering you accept the conditions described in our corporate privacy policy.
Making a small comparison, to get a label on a screen with the old framework the steps would have been

  • Adding TextView to xml layout:
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id=”id_text_view”
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/string_name" />
</LinearLayout>
  • Inflate of the layout (for example in a fragment):
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.layout_id, container, false)
  • In case you want to modify the text added directly with the resource in the XML file:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     view.findViewById<TextView>(R.id.id_text_view).text = "New text"
}

With a significant increase in lines of code for very complex layout. Obviously over the years Google has introduced new methods to try to simplify these steps, for example introducing the ViewBinding/DataBinding (with a concept of updating the ui very similar to the state management present in Compose which we will discuss in the next articles).

Now let’s move on to the Buttons! 

The basic clickable button of the framework is called Button and has several parameters to customize it including: shape, colors, border and many others.

There are also different types of Button such as TextButton and OutlinedButton that already include a theme that follows the guideline of the Material Design, so as not to have to create the button from scratch every time. 🥳

In our example we will use a standard Button, so let’s create our usual method and insert the Button.

@Composable
fun MyFirstComposeButton() {
    Button(onClick = { /*TODO*/ }) {
        //Content to add (for example a Label)
    }
}

By default, the Button provides a “content” parameter for the addition of text that is annotated internally with @Composable, in order to allow you to add a label of your choice. The method provides you a “RowScope”, resulting in the possibility of putting multiple elements on a single line such as Icon (left) + Text, Text only, Text + Icon (right), etc.

Thus obtaining a method of this type:

@Composable
fun MyFirstComposeButton() {
    Button(onClick = { /*TODO*/ }) {
        Text(text = "Click Me!")
        Icon(painter = painterResource(id = R.drawable.resource_name), contentDescription = "")
    }
}

Now we have a button with a text and an icon of your choice to the right of the text, but it will not perform any operation. How to do that? Simple, just invoke the method or perform the action of your choice inside the lambda generated by the onClick parameter of our button, as you can see below:

@Composable
fun MyFirstComposeButton() {
    var counter = 0
    Button(onClick = { counter++ }) {
        Text(text = "Click Me!")
        Icon(painter = painterResource(id = R.drawable.resource_name), contentDescription = "")
    }
}

Wanting to make a comparison as done for the label, in order to add a button with the old framework and manage its click, the steps would have been:

  • Adding Button to a XML layout:
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <Button
        android:id="@+id/id_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me!"/>
</LinearLayout>
  • Inflate of the layout (for example in a fragment):
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.layout_id, container, false)
  • Handle button click:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    var counter = 0
    view.findViewById<Button>(R.id.id_button).setOnClickListener {
        counter++
    }
}

As you can see from the two comparisons I have proposed, you can appreciate the huge savings in lines of code and the simplicity of writing an app using Compose. 😉

Now it’s your turn, try using Text and Button and have fun customising them as much as possible! 😎

Greetings to all of you and see you again in the next “chapter”! 🧑🏻‍💻🤫