Nel caso ancora non aveste letto l’introduzione a Compose vi suggerisco di leggere questo articolo.

Come promesso, eccomi qui con un secondo articolo riguardo Jetpack Compose.

Cominciamo così ad entrare un po’ più nel tecnico vedendo i primi componenti messi a disposizione dal framework.

Alla base di ogni singola app troviamo le Label e i Button, i componenti più semplici che possono essere usati per comporre la UI della nostra app.

Partiamo con le Label.

Chi di voi non si è mai ritrovato a dover aggiungere una TextView ad un xml per visualizzare una label? I passi, all’ordine del giorno per milioni di sviluppatori al mondo, sarebbero: Inserimento di una TextView nell’xml, associarle un id, recuperare la view associata tramite findViewById e se necessario memorizzarla nel nostro Fragment/Activity/Custom View. Ricordando anche la necessità di eseguire l’inflate del layout. 

Ora, però, potete dire addio a tutto ciò e aggiungere una label in due o tre righe di codice.

Innanzitutto va fatta una premessa: Ogni componente grafico che vorrete creare o usare dovrà essere “contenuto” in un metodo annotato con @Composable ad esempio

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

Così facendo il compilatore e il Linter sono in grado di riconoscere l’ambiente di lavoro di Compose suggerendovi i metodi utilizzabili. Come vi verrà anche suggerito dal Linter, il nome di un metodo annotato con `@Composable` deve avere la prima lettera maiuscola mentre il nome di un metodo, che però ritorna un qualsiasi oggetto (una view, una stringa, ecc..), dovrà avere la prima lettera minuscola ad esempio:

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

Returni

Tornando alle nostre label;

Creiamo quindi un metodo che servirà per renderizzare la nostra label

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

e al suo interno andiamo ad aggiungere il componente chiamato Text equivalente alla vecchia TextView

@Composable
fun MyFirstComposeLabel() {
    Text()
}

A questo punto Android Studio ci suggerirà diverse modalità di utilizzo di questo componente come l’utilizzo tramite String classica oppure una AnnotatedString, con la quale si ha un’elevata possibilità di personalizzazione (dimensione del font, colore, ecc) per l’intero testo o per solo alcune porzioni.

Iniziamo usando una String normale e tramite dichiarazione utilizziamo il parametro text assegnandogli la stringa desiderata

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

Nel caso in cui la stringa sia stata inserita in un file di risorse, è possibile usare il metodo stringResource messo a disposizione dal framework

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

Per quanto riguarda le AnnotatedString , il codice si complica di qualche riga da scrivere.

Come accennavo in precedenza, le AnnotatedString sono l’equivalente delle vecchie SpannableString con le quali si può ottenere un alto livello di personalizzazione delle stringhe: porzioni di testo in bold, porzioni cliccabili e molto altro.

Creiamo quindi il nostro solito metodo e andiamo a richiamare il builder per una AnnotatedString utilizzando il metodo buildAnnotatedString.

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

In questo modo, all’interno del nostro builder, avremo la possibilità di usare metodi come append, withStyle e withAnnotation

In questo esempio vogliamo evidenziare la porzione di testo composta da “privacy policy” nella frase “Registrandoti accetti le condizioni descritte nella nostra privacy policy aziendale” utilizzando i metodi append e withStyle.

Le stringhe non le recuperiamo dalle resources per semplicità ma anche durante la creazione di una AnnotatedString è possibile ottenere la vostra resource utilizzando il metodo stringResource.
Il risultato sarà:

@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)
}

che stampato a schermo sarà: Registrandoti accetti le condizioni descritte nella nostra privacy policy aziendale.

Facendo un piccolo confronto, per ottenere una label in una schermata con il vecchio framework i passi sarebbero stati:

  • Aggiunta TextView a layout XML:
<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 del layout (per esempio in un fragment):
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.layout_id, container, false)
  • Nel caso in cui si volesse modificare il testo aggiunto direttamente con la resource nel file XML:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     view.findViewById<TextView>(R.id.id_text_view).text = "New text"
}

Con un notevole incremento di righe di codice per layout molto complessi. Ovviamente nel corso degli anni Google ha introdotto nuovi metodi per cercare di semplificare questi passaggi per esempio introducendo il ViewBinding/DataBinding (con un concetto di aggiornamento della ui molto simile alla gestione a stati presente in Compose di cui parleremo nei prossimi articoli).

Passiamo ora ai Button! 

Il bottone cliccabile base del framework si chiama Button e presenta diversi parametri per personalizzarlo tra cui: shape, colors, border e molti altri.

Esistono inoltre diversi tipi di Button come TextButton e OutlinedButton che includono già un tema che segue le linee guida del Material Design, così da non dover creare da zero il bottone ogni volta. 🥳

Nel nostro esempio useremo un Button standard, andiamo quindi a creare il nostro solito metodo e inseriamo il Button.

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

Di default, il Button prevede l’aggiunta del testo mettendo a disposizione un parametro “content” che è annotato internamente con @Composable, così da permettervi l’aggiunta di una label a vostro piacere. Il metodo esposto vi mette a disposizione un “RowScope”, ovvero la possibilità di mettere più elementi su una sola riga come ad esempio
Icona (a sinistra) + Testo, solo testo, Testo + Icona (a destra), ecc

Ottenendo quindi un metodo di questo tipo:

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

Ora abbiamo un bottone con un testo e un’icona a vostra scelta alla destra del testo, ma non eseguirà nessuna operazione. Come fare? Semplice, vi basterà invocare il metodo o eseguire l’azione che preferite all’interno della lambda generata dal parametro onClick del nostro Button, come potete vedere qui di seguito:

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

Volendo effettuare un confronto come fatto per la label, per poter aggiungere un button con il vecchio framework e gestirne il suo click i passaggi sarebbero stati:

  • Aggiunta Button a layout XML:
<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 del layout (per esempio in un fragment):
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.layout_id, container, false)
  • Gestire il click del bottone:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    var counter = 0
    view.findViewById<Button>(R.id.id_button).setOnClickListener {
        counter++
    }
}

Come si può notare dai due confronti che vi ho proposto, si nota il grosso risparmio di righe di codice e la semplicità di scrittura di un’app mediante Compose. 😉

Ora tocca a voi, provate ad usare Text e Button e divertitevi a personalizzarli il più possibile! 😎

Un saluto a tutti voi e ci rivediamo al prossimo “capitolo”! 🧑🏻‍💻🤫