Android Apps schlank halten: Data Binding und RecyclerView optimal nutzen
Einführung in Android RecyclerView mit Data Binding
In diesem Beitrag zeigen wir, wie man RecyclerView in einer Android-App mit Data Binding integriert. Data Binding reduziert erheblich den Boilerplate-Code, was die Entwicklung effizienter und lesbarer macht. Hier erklären wir, wie Data Binding in Kombination mit dem ViewHolder-Pattern genutzt wird und wie sich Adapter-Klassen dadurch leicht verallgemeinern lassen. Schließlich demonstrieren wir, wie man ein Adapter-Objekt direkt im XML-Code übergibt.
Los geht’s: Erste Schritte
Bevor wir beginnen, müssen wir sicherstellen, dass Data Binding in unserer Android-App aktiviert ist. Dazu fügen wir folgenden Code in die build.gradle-Datei ein:
android{
    ...
    dataBinding {
        enabled = true
    }
    ...
}
Zusätzlich fügen wir folgende Abhängigkeit hinzu:
implementation 'com.android.support:design:28.0.0'
Projektstruktur
In der folgenden Anwendung werden wir die Daten direkt aus dem XML in den Adapter-Zeilen der RecyclerView laden. Außerdem setzen wir die onClickListener-Methoden direkt in den Layout-Zeilen.
Code für das Datenmodell
Der Code für die DataModel.java-Klasse sieht folgendermaßen aus:
package com.beispiel.androidrecyclerviewdatabinding;
public class DataModel {
    public String androidVersion, androidName;
    public DataModel(String androidName, String androidVersion) {
        this.androidName = androidName;
        this.androidVersion = androidVersion;
    }
}
Layout für die Hauptaktivität
Das Layout für die activity_main.xml-Datei ist wie folgt definiert:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto">
    <data>
        <!-- Hier könnten Daten gebunden werden -->
    </data>
    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </android.support.constraint.ConstraintLayout>
</layout>
Hauptaktivität in Java
Der Code für die MainActivity.java-Datei ist wie folgt:
package com.beispiel.androidrecyclerviewdatabinding;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import com.beispiel.androidrecyclerviewdatabinding.databinding.ActivityMainBinding;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        populateData();
    }
    private void populateData() {
        List dataModelList = new ArrayList<>();
        dataModelList.add(new DataModel("Android Oreo", "8.1"));
        dataModelList.add(new DataModel("Android Pie", "9.0"));
        dataModelList.add(new DataModel("Android Nougat", "7.0"));
        dataModelList.add(new DataModel("Android Marshmallow", "6.0"));
        MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(dataModelList, this);
        binding.setMyAdapter(myRecyclerViewAdapter);
    }
}
Layout für jede Zeile in der RecyclerView
Das Layout für jede Zeile in der RecyclerView ist in item_row.xml definiert:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="model"
            type="com.beispiel.androidrecyclerviewdatabinding.DataModel" />
        <variable
            name="itemClickListener"
            type="com.beispiel.androidrecyclerviewdatabinding.CustomClickListener" />
    </data>
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{() -> itemClickListener.cardClicked(model)}"
        app:cardUseCompatPadding="true">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_margin="8dp"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tvAndroidName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{model.androidName}"
                android:textAppearance="@style/TextAppearance.AppCompat.Headline" />
            <TextView
                android:id="@+id/tvAndroidVersion"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{model.androidVersion}"
                android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
        </LinearLayout>
    </android.support.v7.widget.CardView>
</layout>
Im obigen Layout wird die DataModel-Referenz sowie eine Referenz auf das CustomClickListener-Interface, dessen Methode innerhalb der CardView aufgerufen wird, übergeben.
CustomClickListener Interface
Das CustomClickListener.java Interface sieht wie folgt aus:
package com.beispiel.androidrecyclerviewdatabinding;
public interface CustomClickListener {
    void cardClicked(DataModel f);
}
Adapter-Klasse für RecyclerView
Die Adapter-Klasse MyRecyclerViewAdapter.java:
package com.beispiel.androidrecyclerviewdatabinding;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Toast;
import java.util.List;
import com.beispiel.androidrecyclerviewdatabinding.databinding.ItemRowBinding;
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> implements CustomClickListener {
    private List<DataModel> dataModelList;
    private Context context;
    public MyRecyclerViewAdapter(List<DataModel> dataModelList, Context ctx) {
        this.dataModelList = dataModelList;
        context = ctx;
    }
    @Override
    public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                               int viewType) {
        ItemRowBinding binding = DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()),
                R.layout.item_row, parent, false);
        return new ViewHolder(binding);
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        DataModel dataModel = dataModelList.get(position);
        holder.bind(dataModel);
        holder.itemRowBinding.setItemClickListener(this);
    }
    @Override
    public int getItemCount() {
        return dataModelList.size();
    }
    public class ViewHolder extends RecyclerView.ViewHolder {
        public ItemRowBinding itemRowBinding;
        public ViewHolder(ItemRowBinding itemRowBinding) {
            super(itemRowBinding.getRoot());
            this.itemRowBinding = itemRowBinding;
        }
        public void bind(Object obj) {
            itemRowBinding.setVariable(BR.model, obj);
            itemRowBinding.executePendingBindings();
        }
    }
    public void cardClicked(DataModel f) {
        Toast.makeText(context, "Sie haben " + f.androidName + " angeklickt",
                Toast.LENGTH_LONG).show();
    }
}
Adapter direkt im XML binden
Dank Data Binding kann der Adapter nun direkt im XML-Code übergeben werden, was den Code in MainActivity.java weiter vereinfacht. In activity_main.xml wird der Adapter wie folgt gesetzt:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="myAdapter"
            type="com.beispiel.androidrecyclerviewdatabinding.MyRecyclerViewAdapter" />
    </data>
    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adapter="@{myAdapter}"
            app:layoutManager="android.support.v7.widget.LinearLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </android.support.constraint.ConstraintLayout>
</layout>
In MainActivity.java wird der Adapter nun folgendermaßen gesetzt:
MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(dataModelList, this);
binding.setMyAdapter(myRecyclerViewAdapter);
Durch diese Technik müssen wir die RecyclerView nicht mehr manuell in der Aktivität initialisieren. Das macht den Code sauberer und die Anwendung effizienter.


