Android Data binding

Il Google I/O si è appena concluso e non so voi ma io sono molto soddisfatto. Dal nostro punto di vista, di sviluppatori,  le novità non sono mancate, anzi sicuramente mancherà  il tempo per studiarle e provarle tutte.
Forse dal punto di vista dell’utente è mancata la novità super cool che fa gridare il solito WOW ma secondo me su Android non serve più.. anche se Now On Tap è davvero impressionante.

Sono rimasto molto sorpreso e contento delle nuove librerie  Support che ci  permetteranno di replicare alcuni pattern design e animazioni di Lollipop utilizzando un minSdk 9.
Mi riferisco a FAB, la Toolbar che  sparisce scrollando le liste, CoordinatorLayout, o la Collapsing Toolbars senza dimenticare la NavigationView.
In questo articolo però non vi parlerò delle support  ma di una libreria che è stata rilasciata, anche se ancora in beta, e che secondo me può rivoluzionare il modo di sviluppare su Android: Data Binding

Data binding: un po’ di teoria

Il concetto di Data Binding non è certamente una novità nel mondo della programmazione. Il Data Binding è il meccanismo che stabilisce una connessione e/o una sincronizzazione tra la UI e la business logic e, volendo sintetizzare, i Model.
Questa connessione permette che il cambiamento della base dati si rifletta sulla UI e nel particolare sulle varie views senza scrivere ulteriori righe di codice. La sincronizzazione ci evita di collegare ogni singola proprietà del model  alla singola view. Il meccanismo è stato usato in diversi linguaggi come C# con Windows Presentation Foundation e, chi come me, ha anche sviluppato con Flex/Flash Builder, ricorderà sicuramente mxml e il data binding nei  vari componenti.
Dopo questa premessa ritorniamo ad Android. La libreria anche se ancora in beta funziona bene e promette altrettanto bene e soprattutto è utilizzabile anche con le versioni di Android meno recenti (API level 7+).

Gli utilizzi del data binding  possono essere centinaia: proverò a farne qualcuno. Classica applicazione multilingua; di solito si creano  le varie cartelle  values-it, values-en e si scrivono i vari xml ma immaginate che tutte le label, testi dei pulsanti etc arrivino da un json caricato dal web. La prassi è di creare i vari model, ottenere le informazioni utilizzando un parser come json o jackson e dato per dato e view per view andare a settare i testi.
Il meccanismo di data binding ci aiuta a semplificare il lavoro; vediamo come.

In questo esempio ho creato un semplice layout: un form di invio contatti. L’esigenza è di avere un layout multilingua e che le label e i testi dei vari campi vengono popolati da un model che è stato precedentemente creato da un parse di un json.
Prepariamo quello che ci serve.

Data binding: il codice

La libreria è in beta e funziona solo utilizzando Android Studio 1.3 preview.
Carichiamo la libreria modificando il file build.gradle del progetto:

dependencies {
    classpath 'com.android.tools.build:gradle:1.2.3'
    classpath "com.android.databinding:dataBinder:1.0-rc0"
}

In ogni modulo dove useremo la libreria è necessario applicare il plugin:

apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

In questo esempio molto semplice non descriverò i metodi per caricare i dati dal web e il parse ma utilizzerò un model creato ad hoc, istanziato e popolato nella classe.
Ecco il model:

public class FieldsForm {

    public String name;
    public String surname;
    public String address;
    public String message;
    public String buttonSend;
    public boolean hasmessage;

    public FieldsForm(String name, String surname, String address, String message, String send, boolean hasmessage) {

        this.name = name;
        this.surname = surname;
        this.address = address;
        this.message = message;
        this.buttonSend = send;
        this.hasmessage = hasmessage;
    }
}

Adesso creiamo il layout.

Per utilizzare il data binding la root del nostro layout deve iniziare con il tag <layout>  e a seguire il tag <data> dove inseriremo il collegamento al model.

<layout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="fieldsForm"
    type="com.giuseppesorce.databinding.models.FieldsForm" />
</data>

All’ interno del tag inseriamo il caricamento del model e useremo la variabile fieldsForm come istanza. Ecco il layout completo:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="fieldsForm"
            type="com.giuseppesorce.databinding.models.FieldsForm"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:text="@{fieldsForm.name}"/>

        <EditText
            android:id="@+id/etName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"/>

        <TextView
            android:id="@+id/tvSurname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:text="@{fieldsForm.surname}"/>

        <EditText
            android:id="@+id/etSurname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"/>

        <TextView
            android:id="@+id/tvAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:text="@{fieldsForm.address}"/>

        <EditText
            android:id="@+id/etAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"/>

        <TextView
            android:id="@+id/tvMessage"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:text="@{fieldsForm.message}"/>

        <EditText
            android:id="@+id/etMessage"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:layout_marginBottom="10dp"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{fieldsForm.buttonSend}"/>
    </LinearLayout>
</layout>

Ogni TextView, EditText dove serve la traduzione del nome del campo settiamo la proprietà text in questo modo:

android:text="<strong>@{model.proprietà}</strong>"

Ora passiamo alla classe; in questo caso l’activity.
Per default viene generata una binding class in base al nome del layout che utilizzeremo. Nel nostro caso ho lasciato il nome main_activity.xml quindi il nome della classe sarà MainActivityBinding.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    FieldsForm form = new FieldsForm("Nome", "Cognome", "Indirizzo", "Messaggio", "Invia", false);

    MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
    binding.setFieldsForm(form);
}

La riga 6 crea l’istanza del model FieldsForm passando le stringhe in italiano ma è possibile utilizzare model creati da una query ad un db o da un servizio Rest.
La riga 8 crea la classe binding utilizzando il metodo DataBindingUtil.setContentView passando il riferimento del layout e nella riga 9 passiamo il model alla classe.
Poche righe di codice per utilizzare i dati del model per popolare il nostro layout. La libreria data binding è uno strumento potente e ci fa risparmiare tempo perchè oltre a popolare semplicemente un layout permette di utilizzare della logica e/o una sintassi per modificare il layout in base alle proprietà del model.

Aggiungiamo una proprietà al model: hasmessage.

Per poter utilizzare i metodi e le proprietà della classe View dobbiamo effettuare un import come in java così:

<data>
    <import type="android.view.View">
</data>

Nel layout cambiamo i due campi relativi al message:

<TextView
    android:id="@+id/tvMessage"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="@{fieldsForm.hasmessage ? View.VISIBLE : View.GONE}"
    android:text="@{fieldsForm.message}" />

<EditText
    android:id="@+id/etMessage"
    android:layout_width="match_parent"
    android:layout_marginBottom="10dp"
    android:visibility="@{fieldsForm.hasmessage ? View.VISIBLE : View.GONE}"
    android:layout_height="100dp" />

In questo esempio ho nascosto il campo message in base alla proprietà hasmessage.
Le espressioni che si possono utilizzare sono tante:

matematiche : + - / * %

logiche: && ||

confronto: == > = <=

Inoltre è possibile utilizzare array, liste e utilizzare altre classi per esempio per rendere maiuscolo la stringa del model.

La guida ufficiale elenca moltissime possibilità di utilizzo: developer.android.com/tools/data-binding/guide.html

Observable Objects

In questa guida ho descritto solo una delle tante possibilità del data binding, forse l’utilizzo più semplice e diretto ma uno dei vantaggi e potenzialità della libreria è l’utilizzo di Observable cioè oggetti che implementano l’interface Observable o che estendono BaseObservable e che rendono la sincronizzazione ancora più efficace. Modificando in runtime il Model la modifica si riflette sulla UI senza preoccuparsi di scrivere altro codice. Aggiungendo al metodo get di una variabile l’annotation @Bindable e utilizzando notifyPropertyChanged nel metodo set qualsiasi modifica della singola proprietà si rifletterà nella UI. Nell’esempio allegato ho modificato la proprietà hasmessage in runtime e la UI si è modificata.

Conclusioni

Ho letto vari commenti molto positivi e qualcuno con qualche dubbio sull’utilizzo di questo pattern. Alcuni sviluppatori non sono d’accordo nell’utilizzare la business logic all’interno del layout. Il degin pattern utilizzato in questo caso diventa View-ViewModel-Model (MVVM).
Secondo me la libreria è molto utile e flessibile e può cambiare il modo di sviluppare su Android, gli automatismi che si possono implementare permetteranno la creazione di layout più dinamici e adatti ad ogni esigenza e di risparmiare righe di codice e ore di lavoro.
Per esempio utilizzando EventBus come Otto sarà possibile fare il refresh dei layout molto velocemente semplicemente passando il model.

Il codice completo dell’esempio utilizzato in questo post è disponibile su github.com/giuseppesorce/databinding.

Giuseppe Sorce

Sviluppo dal 1999 in numerosi linguaggi da java ad asp da actionscript a javascript ma solo nel 2009 incontro la piattaforma che amo di più: Android. Più che per lavoro sviluppo per passione.