• 20 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 12/12/19

Implémentez votre première RecyclerView

Vous devez sûrement maintenant être très à l'aise pour créer des appels réseau performants et les afficher dans un TextView. Cependant, l'affichage dans un TextView n'est pas vraiment idéal... En effet, vos utilisateurs méritent mieux ! Ils méritent une RecyclerView !

Qu'est-ce qu'une RecyclerView ?

Digne successeure de la ListView et de la GridView, la RecyclerView s'impose en étant un pattern plus performant et permettant une personnalisation plus poussée que ces prédécesseures. 

Pour fonctionner, cette dernière a besoin de trois composants obligatoires lors de son implémentation :

  • Un Adapter (RecyclerView.Adapter) : Permet de faire la liaison (Bind) entre la vue RecyclerView et une liste de données.

  • Un LayoutManager (RecyclerView.LayoutManager) : Permet de positionner correctement l'ensemble des données de la liste.

  • Un ViewHolder (RecyclerView.ViewHolder) : Permet de représenter visuellement un élément de la liste de données dans le RecyclerView (Une ligne).

Architecture d'une RecyclerView
Architecture d'une RecyclerView

Implémenter une RecyclerView

Reprenons notre application NetApp (disponible également à ce commit Github). Nous allons lui implémenter une belle RecyclerView pour par la suite, y ajouter des données (la liste des utilisateurs suivis par Jake Wharton).

On commence par installer la librairie de support Android introduisant la RecyclerView grâce à notre gestionnaire de dépendance Gradle.

Extrait de build.gradle :

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:26.1.0'
}

Modifions maintenant le layout de notre fragment MainFragment afin de lui ajouter une RecyclerView.

Fichier layout fragment_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/fragment_main_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Explications : Nous avons ici complètement changé le layout du fragment MainFragment. Ce dernier ne contiendra maintenant qu'un RelativeLayout qui lui-même contiendra une RecyclerView prenant toute la place disponible sur l'écran.

Puis, nous allons créer un layout XML représentant chaque item (chaque ligne) de notre RecyclerView.

Fichier layout fragment_main_item.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dip">

    <TextView
        android:id="@+id/fragment_main_item_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

</LinearLayout>

Explications : Chaque ligne de notre liste, donc chaque item, contiendra pour le moment un simple TextView qui affichera le nom de chaque utilisateur suivis par Jake Wharton.

Puis nous allons créer le ViewHolder représentant l'objet désérialisé de ce layout, afin de pouvoir plus facilement le modifier en JAVA. Pour cela, créez dans le package Views de votre application NetApp, la classe GithubUserViewHolder.java.

Classe Views/GithubUserViewHolder.java :

public class GithubUserViewHolder extends RecyclerView.ViewHolder {

    @BindView(R.id.fragment_main_item_title) TextView textView;

    public GithubUserViewHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }

    public void updateWithGithubUser(GithubUser githubUser){
        this.textView.setText(githubUser.getLogin());
    }
}

Explications : Nous avons créé un ViewHolder héritant de  RecyclerView.ViewHolder  . Le but ici est de modéliser en un objet JAVA la précédente vue XML créée (représentant une ligne de la RecyclerView). Puis, nous avons créé une méthode publique qui va nous permettre de modifier cette vue en fonction d'un objet  GithubUser  passé en paramètre (le nom de chaque utilisateur Github sera affiché dans le TextView).

Maintenant, il ne nous reste plus qu'à lier tout cela ensemble grâce à un Adapter. Ainsi, dans le package Views de notre application, nous allons créer un Adapter appelé GithubUserAdapter.java.

Classe Views/GithubUserAdapter.java :

public class GithubUserAdapter extends RecyclerView.Adapter<GithubUserViewHolder> {

    // FOR DATA
    private List<GithubUser> githubUsers;

    // CONSTRUCTOR
    public GithubUserAdapter(List<GithubUser> githubUsers) {
        this.githubUsers = githubUsers;
    }

    @Override
    public GithubUserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // CREATE VIEW HOLDER AND INFLATING ITS XML LAYOUT
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.fragment_main_item, parent, false);

        return new GithubUserViewHolder(view);
    }

    // UPDATE VIEW HOLDER WITH A GITHUBUSER
    @Override
    public void onBindViewHolder(GithubUserViewHolder viewHolder, int position) {
        viewHolder.updateWithGithubUser(this.githubUsers.get(position));
    }

    // RETURN THE TOTAL COUNT OF ITEMS IN THE LIST
    @Override
    public int getItemCount() {
        return this.githubUsers.size();
    }
}

Explications :  Nous avons créé un Adapter, GithubUserAdapter, héritant de RecyclerView.Adapter, et qui nous permettra de faire le lien entre la vue RecyclerView et notre contrôleur (MainFragment). Très généralement, un Adapter se construit avec la référence d'une liste d'objets passée en paramètre depuis le contrôleur. Puis, on retrouve trois méthodes importantes :

  • onCreateViewHolder()  : Elle nous permet de créer un ViewHolder à partir du layout XML représentant chaque ligne de la RecyclerView. Celle-ci sera appelée pour les premières lignes visibles de la RecyclerView.

    Pourquoi pas les autres ? Tout simplement car la RecyclerView possède un système permettant de réutiliser (ou recycler... ;) ) les ViewHolder déjà créés. Il faut savoir que la création d'une vue sur Android est une action qui demande beaucoup de ressources. Imaginez donc avoir 1000 lignes à afficher dans votre application : sans ce mécanisme de réutilisation, cette dernière souffrirait de ralentissements assez importants.

  • onBindViewHolder()  : Cette méthode est appelée pour chacune des lignes visibles affichées dans notre RecyclerView.  C'est généralement ici que l'on met à jour leur apparence. Dans notre cas nous appelons la méthode du ViewHolder que nous avons précédemment créée, afin de mettre à jour son TextView à partir d'un GithubUser. D'ailleurs, nous avons grâce à la variable position, récupéré l'objet GithubUser correspondant dans notre liste d'objet.

  • getItemCount()  : Cette méthode permet de retourner la taille de notre liste d'objet, et ainsi indiquer à l'Adapter le nombre de lignes que peut contenir la RecyclerView.

Maintenant, passons à la configuration de notre fragment afin de lier tout ce beau monde... :) Nous allons ici faire en sorte de récupérer tous les utilisateurs suivis par Jake Wharton au lancement de notre application, puis les afficher dans la RecyclerView. Faisons-donc un peu de tri !

Classe complète MainFragment.java :

public class MainFragment extends Fragment {

    // FOR DESIGN
    @BindView(R.id.fragment_main_recycler_view) RecyclerView recyclerView; // 1 - Declare RecyclerView

    //FOR DATA
    private Disposable disposable;
    // 2 - Declare list of users (GithubUser) & Adapter
    private List<GithubUser> githubUsers;
    private GithubUserAdapter adapter;

    public MainFragment() { }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        ButterKnife.bind(this, view);
        this.configureRecyclerView(); // - 4 Call during UI creation
        this.executeHttpRequestWithRetrofit(); // 5 - Execute stream after UI creation
        return view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.disposeWhenDestroy();
    }

    // -----------------
    // CONFIGURATION
    // -----------------

    // 3 - Configure RecyclerView, Adapter, LayoutManager & glue it together
    private void configureRecyclerView(){
        // 3.1 - Reset list
        this.githubUsers = new ArrayList<>();
        // 3.2 - Create adapter passing the list of users
        this.adapter = new GithubUserAdapter(this.githubUsers);
        // 3.3 - Attach the adapter to the recyclerview to populate items
        this.recyclerView.setAdapter(this.adapter);
        // 3.4 - Set layout manager to position the items
        this.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    }

    // -------------------
    // HTTP (RxJAVA)
    // -------------------

    private void executeHttpRequestWithRetrofit(){
        this.disposable = GithubStreams.streamFetchUserFollowing("JakeWharton").subscribeWith(new DisposableObserver<List<GithubUser>>() {
            @Override
            public void onNext(List<GithubUser> users) {
                // 6 - Update RecyclerView after getting results from Github API
                updateUI(users);
            }

            @Override
            public void onError(Throwable e) { }

            @Override
            public void onComplete() { }
        });
    }

    private void disposeWhenDestroy(){
        if (this.disposable != null && !this.disposable.isDisposed()) this.disposable.dispose();
    }

    // -------------------
    // UPDATE UI
    // -------------------

    private void updateUI(List<GithubUser> users){
        githubUsers.addAll(users);
        adapter.notifyDataSetChanged();
    }
}

Explications : Nous avons ici légèrement modifié notre contrôleur MainFragment en y-ajoutant la RecyclerView puis en appelant la stream qui récupère les utilisateurs suivis par Jake Wharton.

  • Ligne 1 : Nous déclarons ici la RecyclerView afin d'y accéder plus facilement.

  • Ligne 2 : Nous déclarons également la liste d'objets (la liste de GithubUser) que nous allons utiliser pour remplir notre RecyclerView, puis nous déclarons également notre Adapter.

  • Ligne 3 : Nous configurons ici notre RecyclerView, notre Adapter ainsi que notre LayoutManager.

    • Ligne 3.1 : Nous initialisons une nouvelle liste d'objets pour notre variable  githubUsers

    • Ligne 3.2 : Nous créons ici un Adapter contenant une référence vers notre liste d'objets  githubUser  .

    • Ligne 3.3 : Nous passons ensuite l'Adapter à la RecyclerView afin que cette dernière puisse enfin afficher des données (pour le moment elle n'affichera rien car la liste d'objets est vide).

    • Ligne 3.4 : Nous définissons un LayoutManager afin de définir à la RecyclerView la manière dont elle doit afficher ses données (Ici grâce à un LinearLayoutManager).

  • Ligne 4 : Nous pensons à appeler cette précédente méthode dans le onCreateView() du fragment.

  • Ligne 5 : On pense également à appeler la stream dans la méthode onCreateView() du fragment.

  • Ligne 6 : A la fin de notre stream, donc une fois que la liste des utilisateurs suivis par Jake Wharton est téléchargée, nous allons l'afficher dans notre RecylerView. Pour cela, nous ajoutons les éléments téléchargées à notre liste d'objets (  addAll(users)  ) puis nous rechargeons notre Adapter grâce à sa méthode  notifyDataSetChanged() .

Lancez maintenant votre application NetApp. Super ! Les données sont bien récupérées sur l'API de Github et celles-ci s'affichent correctement dans la RecyclerView.

Résultat
Résultat

En revanche, le design n'est pas forcément top... Ne vous inquiétez pas, on s'en occupe dès le prochain chapitre.

Example of certificate of achievement
Example of certificate of achievement