
Ătapes du Dojo
Application de découverte de films:
Durant les prochaines heures, nous allons créer une application Android.
Cette application utilisera l'API mise à disposition par la base de données de films The Movie Database (TMDb) pour afficher sous forme de carte :
- Les films au cinéma actuellement
- Le top des films de l'année
- Les films Ă venir
En bonus (đ), il sera aussi possible d'ajouter ces films en favoris afin de garder une sĂ©lection de film dans l'application.
Architecture du code
Model View Presenter (MVP) + Repository
- View:
La View correspond à l'interface que l'utilisateur aura sous les yeux. Elle est en charge d'informer le Presenter des actions de l'utilisateur et d'afficher les données récupérées. Elle est matérialisée ici par une interface
<name>ViewContractqui permet d'abstraire les actions possibles sur notre vue.
- Presenter:
Le Presenter reçoit les actions de l'utilisateur et sera en charge de récupérer les données pertinentes du Model dans le Repository. Il permet de faire le lien entre la View et le Model.
- Model:
Le Model regroupe l'ensemble des ressources métiers de notre application qui seront affichées.
- Repository:
Le Repository va servir à récupérer les données de l'application de diverses maniÚres, dans une base de données, en cache ou encore sur des API.

Executors (threading)
Sur Android, il est impossible de faire des appels réseaux sur le main thread (thread en charge de la vue, il est en charge d'afficher l'application avec un certain nombre de frame par seconde).
Il faut donc passer les appels réseau sur un autre thread puis envoyé la réponse sur le main thread.
Pour facilité la gestion des threads, en s'inspirant des recommandations de Google en terme d'architecture d'application, la classe AppExecutors a été créée. Elle permet d'avoir accÚs à 3 types d'executors qui permet de lancer des actions sur 3 types de threads: le main thread, le thread network IO et le thread disk IO.
Activité:
An Activity is a single, focused thing that the user can do. (https://developer.android.com/reference/android/app/Activity)Une application Android, est composĂ©e dâune ou plusieurs activitĂ©s (et de fragments).
Ces objets suivent un cycle de vie dĂšs lors que lâapplication est lancĂ©e.
onCreate:C'est ici que nous allons créer la vue de notre activité et initialiser les vues de cette derniÚre.
onStart:Démarrage de l'activité, c'est ici qu'on va appeler le presenter pour aller chercher les données.
onStop: (symĂ©trique deonCreate)Il faut informer le presenter que l'activitĂ© est arrĂȘtĂ©e, et donc qu'il ne faut pas essayer de la mettre Ă jour si des donnĂ©es cherchĂ©es sont disponibles.
onDestroy: (symétrique deonCreate)L'activité est détruite.

https://developer.android.com/guide/components/activities/activity-lifecycle
1. Création de la carte d'un film
git stash -a && git checkout step1.0Inflate un layout dans une activité
A layout defines the structure for a user interface in your app, such as in an activity. All elements in the layout are built using a hierarchy of View and ViewGroup objects. A View usually draws something the user can see and interact with. (https://developer.android.com/guide/topics/ui/declaring-layout)
La façon la plus utilisée pour créer l'interface utilisateur de notre activité est d'utiliser un layout.
C'est un fichier XML qui définit la structure de notre interface utilisateur.
app/res/layout/activity_discover.xmlVous devriez voir l'écran ci-dessous:

Comme vous pouvez le voir, un layout ressemble plus ou moins à un fichier HTML. On a ici un ViewGroup, le FrameLayout qui contient tout simplement des vues sans arrangement spécial, et une View, la TextView qui permet d'afficher du texte.
Pour utiliser ce layout dans notre activité, il faut utiliser la méthode setContentView lors de la création de l'activité.
com.fabernovel.codingdojo.app.main.DiscoverActivity et utiliser setContentView pour inflate le layout activity_discover. R.<type de la ressource>.<nom de la ressource>.
Ici ce sera donc R.layout.activity_discoverCTRL+N / CMD + O
Pour chercher une fichier, utiliser CTRL+SHIFT+N / CMD+SHIFT+NCréation de la carte d'un film
git stash -a && git checkout step1.2Le but maintenant va ĂȘtre d'afficher la carte ci-dessous:


layout_width et layout_height .
La valeur de ces attributes peut-ĂȘtre:
- Un dimension (en dp de préférences exemple: layout_width="56dp" )
- wrap_content : Android calcule la taille de vue minimale possible.
- match_parent : la vue prend tout l'espace dans le parents.Pour faire la carte en elle-mĂȘme, nous allons utiliser une CardView
CardView vient de la librairie de material design de google qui a déjà été ajoutée dans le projet.
(https://material.io/develop/android/docs/getting-started/)Afin d'organiser les éléments au sein de cette CardView, nous allons utiliser des LinearLayout. Grùce à ce layout, il est possible d'aligner des vues de façon horizontales ou bien vertical.
<!-- VERTICAL -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<!-- VIEWS -->
</LinearLayout>
<!-- HORIZONTAL -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<!-- VIEWS -->
</LinearLayout>Pour les textes, nous allons utiliser des TextView. Le titre du film utilisera android:textAppearance="@style/Headline6" , les label (date de sortie et genre)android:textAppearance="@style/Label" et les valeurs de ces labels: android:textAppearance="@style/Caption" .
Le texte affiché par les labels se trouve dans le fichier de resource res/values/string.xml . Pour les utiliser dans le layout, ajouter un attribut: android:text="@string/<nom de la ressource>" .
Pour les Ă©toiles de la note du film, une RatingBar, sur laquelle on utilisera l'attribut isIndicator="true" pour empĂȘcher les utilisateurs de modifier le nombre d'Ă©toiles.
Vues Ă ajouter dans le layout
| Vue | ID | Type de vue | Commentaire | Variable |
|---|---|---|---|---|
| Poster | movie_card_poster | ImageView | moviePoster | |
| Titre | movie_card_title | TextView | movieTitle | |
| Ătoiles | movie_card_stars | RatingBar | - Ajouter un style: style="@style/Widget.AppCompat.RatingBar.Small";
- Ajouter android:isIndicator="true" | movieRatingBar |
| Note | movie_card_rating | TextView | movieRating | |
| Label de date de sortie | movie_card_release_date_title | TextView | ||
| Date de sortie | movie_card_release_date_value | TextView | movieReleaseDateValue | |
| Label du genre | movie_card_genre_title | TextView | ||
| Genre | movie_card_genre_value | TextView | movieGenreValue | |
| Message de loading | discover_message | TextView | discoverMessage | |
| Carte | movie_card | CardView | movieCard |
item_movie dans le dossier app/res/layout.
La vue sera composée d'une CardView parente dans laquelle se trouvera un premier LinearLayout horizontal qui contiendra le poster et un second LinearLayout vertical qui contiendra les autres vues.
Pour le poster, il est possible d'utiliser l'image joker.png mise Ă disposition.
Dans le layout activity_discover, ajouter <include layout="@layout/item_movie" /> au dessus de la vue discoverMessage et ajouter android:visibility="gone" sur la vue discoverMessage.2. Afficher les données des films dans la carte créée
git stash -a && git checkout step2.0Pour l'instant, les données affichées sont en dur dans le layout.
Le but de cette partie sera de récupérer les vues de la carte de film dans l'activité et d'y mettre les valeurs venant d'un objet Movie.
Récupérer les vues d'un layout
findViewById qui permet de rechercher une vue avec un certain id dans toute la hiérarchie de la vue utilisée par notre activité avec setContentView.
https://developer.android.com/reference/android/app/Activity#findViewById(int)
mentionner R.id. ?dGV
DiscoverActivity, créer des variables permettant de récupérer les différentes vues de notre carte film (poster, note, étoiles, titre, genre, etc).
Initialiser ces variables aprĂšs l'appel Ă setContentView.Pour rappel sur les types de vue:
Vues Ă ajouter dans le layout
| Vue | ID | Type de vue | Variable | Commentaire |
|---|---|---|---|---|
| Genre | movie_card_genre_value | TextView | movieGenreValue | |
| Label du genre | movie_card_genre_title | TextView | ||
| Date de sortie | movie_card_release_date_value | TextView | movieReleaseDateValue | |
| Label de date de sortie | movie_card_release_date_title | TextView | ||
| Note | movie_card_rating | TextView | movieRating | |
| Ătoiles | movie_card_stars | RatingBar | movieRatingBar | - Ajouter un style: style="@style/Widget.AppCompat.RatingBar.Small";
- Ajouter android:isIndicator="true" |
| Titre | movie_card_title | TextView | movieTitle | |
| Poster | movie_card_poster | ImageView | moviePoster | |
| Message de loading | discover_message | TextView | discoverMessage | |
| Carte | movie_card | CardView | movieCard |
Afficher du contenues dans des vues
git stash -a && git checkout step2.2Maintenant que les vues ont bien été récupérées, il reste à récupérer un object Movie à afficher. Pour cela utiliser la classe DiscoverPresenter.
DiscoverViewContract et override ses méthodes dans DiscoverActivity:
1. showLoading: afficher le message R.string.loading dans la vue R.id.discoverMessage, puis changer sa visibilité (setVisibility ) à View.VISIBLE et celle de la carte film à View.GONE.
2. showContent: afficher les différentes propriétés du paramÚtre Movie dans les vues correspondantes puis changer sa visibilité à View.VISIBLE et celle du message à View.GONE
3. showError: afficher le message en paramÚtre dans la vue R.id.discoverMessage, puis changer sa visibilité (setVisibility ) à View.VISIBLE et celle de la carte film à View.GONE.Picasso.get().load(model.getPosterPath()).into(moviePoster); . Picasso permet de charger des images dans des ImageView .
- Pour mettre du texte dans une TextView, utiliser la méthode setText(<text>) .
- Pour mettre en forme la date de sortie, utiliser DateUtils.formatDate(<date>) qui formate la date en chaĂźne de caractĂšres
- Pour mettre le nombre d'étoile de la RatingBar, utiliser le méthode setRating(<note>)Il faut maintenant récupérer et initialiser le DiscoverPresenter.
DiscoverActivity, créer une variable de type DiscoverPresenter initialisée dans le onCreate en utilisant la méthode statique getInstance(this). (cela permet de ne pas exposer le repository à la vue)
2. Dans la méthode onStart appeler la méthode start du presenter en lui passant le view contract (ici this puisque notre activité implémente ce dernier).
3. Dans la mĂ©thode onStop, juste avant l'appel Ă super.onStop(), appeler la mĂ©thode stop du prĂ©senter. Cela permet, Ă la vue de ne pas ĂȘtre mise Ă jour lorsqu'elle n'est plus visible.RĂ©cupĂ©rer un film dans le repository
git stash -a && git checkout step2.3Le MovieRepository fourni permet de récupérer un film. Pour l'instant, le film est déclaré en dur dans le repository.
MovieRepository, l'utilisation d'un callback est nécessaire. En effet, la récupération d'un film est asynchrone et il faut attendre d'obtenir le résultat pour l'afficher.
Le callback est ici déclaré dans l'interface GetMovieCallback qui comporte deux méthodes onGetMovie lorsque le film a été récupéré et onError en cas d'erreur.
Le repository va récupérer les films en utilisant le thread Network I/O puis appeler le callback sur le main thread.start du presenter:
1. Afficher l'écran de chargement du view contract
2. Dans l'attribut movieCallback de type GetMovieCallback et appeler respectivement showContent et showError en cas de succĂšs (onGetMovie) et d'erreur (onError).
3. Récupérer le film avec le repository: movieRepository.getMovie(movieCallback)3. Afficher une liste de films
git stash -a && git checkout step3.0Pour l'instant, le layout qu'on a mis en place ne supporte que l'affichage d'une seule carte de film.
RecyclerView
https://developer.android.com/guide/topics/ui/layout/recyclerviewapp/res/layout/activity_discover.xml, remplacer le bloc qui permettait d'afficher la carte de film (<include layout"@layout/item_movie">)par une RecyclerView avec l'id movie_recycler .
2. Faites les changements nécessaires dans la classe DiscoverActivity pour récupérer la RecyclerView dans l'activité, et ensuite gérer sa visibilité en fonction de l'état de la vue : loading, content, etc.
Si besoin, se référer à la section Add RecyclerView to your layout.ViewHolderPasser la liste de films à la RecyclerView
git stash -a && git checkout step3.1Pour pouvoir fonctionner, une RecyclerView s'utilise toujours avec un Adapter qui gĂšre la liste des Ă©lĂ©ments Ă afficher. L'Adapter a Ă son tour besoin d'un ViewHolder qui lui gĂšre l'affichage d'un Ă©lĂ©ment de la liste. Par souci d'efficacitĂ©, ces 2 classes vous ont Ă©tĂ© fournies et sont prĂȘtes Ă ĂȘtre utilisĂ©es. Quand on implĂ©mente une recycler view pour la premiĂšre fois, on peut avoir un peu de mal Ă comprendre comment tout cela fonctionne, voici donc quelques explications :
Le ViewHolder
Un view holder a simplement pour but de gérer l'affichage d'un élément de la liste. Si vous jetez un coup d'oeil sur la classe MovieViewHolder, vous verrez que son constructeur prend en paramÚtre une View. Cette vue correspond à une carte de film, et en appelant la méthode findViewById du paramÚtre itemView avec les bons ids, on récupÚre séparément des références sur les vues contenues dans la carte : la TextView qui affiche le titre, l'ImageView qui contient le poster, etc. Ces références sont utilisées plus tard dans la méthode bind() pour alimenter chaque carte de film avec les données d'un élément de la liste. C'est là qu'entre en jeu l'Adapter.
L'Adapter
Un adapter a pour rÎle de faire le lien entre une liste d'objets de type Movie et les ViewHolders qui vont afficher chacun des éléments de la liste. Si vous jetez un coup d'oeil sur la classe MoviesAdapter, vous verrez qu'il contient les éléments suivants :
- un attribut de type
List<Movie>dataSet qui permet de stocker la liste de films.
- une méthode
onCreateViewHolder(@NonNull ViewGroup parent, int viewType)qui sert, comme son nom l'indique, à créer un ViewHolder, et donc à inflate le bon layout pour un élément de la liste en particulier. La vue à afficher dans leMovieViewHolderest celle contenue dans le layoutitem_movie.
- une méthode
onBindViewHolder(@NonNull MovieViewHolder holder, int position)qui permet de passer un objet de typeMovieĂ unMovieViewHolder.
viewType qui nous aurait permis de choisir le layout et la classe du ViewHolder en fonction de l'élément à afficher.Récupérer une liste de films dans le repository
Pour tester notre recycler view, utiliser la méthode getMovies() de MovieRepository (qui retourne une liste de 3 films) à la place de getMovie() dans le presenter.
showContent() (dans DiscoverViewContract et DiscoverActivity).
Dans DiscoverPresenter remplacez le GetMovieCallback par GetMoviesCallback pour récupérer une liste de film.Passer la liste à l'Adapter
DiscoverActivity, déclarer un attribut de type MoviesAdapter et le relier à la recycler view en utilisant la méthode setAdapter(Adapter adapter) de la classe RecyclerView.
2. Dans la méthode showContent de DiscoverActivity, transmettre la liste reçue en paramÚtre à l'Adapter via submitList().4. Utiliser une API pour chercher les films à afficher
Comme vous vous en doutez, Ă©crire nous mĂȘme la liste des films Ă afficher n'a pas trop d'intĂ©rĂȘt. Pour que notre application devienne vraiment utile, on va se servir de la base de donnĂ©es (non-officielle) disponible ici The Movie Database (TMDb) Plus prĂ©cisĂ©ment, on va utiliser l'API REST qui expose diffĂ©rents endpoints qui permettent d'interroger la base de donnĂ©es de diffĂ©rentes maniĂšres. La documentation exhaustive de cette API est disponible ici https://developers.themoviedb.org/3/getting-started
Découvrir l'API
Avant de l'intégrer dans le code de notre application, il est pertinent de prendre quelques minutes pour interagir avec L'API et comprendre son fonctionnement.
Pour ce dojo, le endpoint /discover/movie suffit Ă notre besoin. Ce endpoint renvoie une liste de films qui correspond Ă la combinaison de filtres (paramĂštres) spĂ©cifiĂ©e dans la requĂȘte qu'on lui envoie.
L'accĂšs Ă l'API se fait Ă l'aide une clĂ© d'API qu'il faut insĂ©rer dans chaque requĂȘte Ă l'aide du paramĂštre api_key.
/discover/movie pour restreindre la liste de films qu'on récupÚre
2. Cette page https://developers.themoviedb.org/3/discover/movie-discover (onglet : Try it out) permet d'interagir avec le endpoint simplement, en insĂ©rant des paramĂštres dans un formulaire. Envoyer une ou deux requĂȘtes et vĂ©rifier que la rĂ©ponse (en format json) est cohĂ©rente (ne pas oublier de renseigner la clĂ© d'api)
3. Quelles sont les 3 combinaisons de paramÚtres qui nous permettront de récupérer les 3 listes de films à afficher dans les 3 onglets de l'app ?
Compléter la définition de l'API
Pour faciliter l'envoi de requĂȘtes et la rĂ©ception des rĂ©ponses, la librairie Retrofit est trĂšs pratique. Vous pouvez jeter un coup d'oeil sur la la page web de Retrofit pour voir comment ça marche (principalement les sections Introduction, REQUEST METHOD, et URL MANIPULATION)
Retrofit est un client REST qui facilite l'Ă©change avec une API REST. Pour pouvoir s'en servir, il faut dĂ©clarer une interface qui dĂ©crit les requĂȘtes qu'on va faire auprĂšs de l'API. Chaque requĂȘte se traduit par un prototype de mĂ©thode, annotĂ©e du type de requĂȘte (@GET, @POST, etc). L'annotation prend en paramĂštre le "path" de la requĂȘte sans la base url. On vous a fourni l'interface (MovieApi) avec la mĂ©thode suivante que nous utiliserons pour chercher les films qui vont sortir bientĂŽt :
@GET("/3/discover/movie")
Call<RestDiscoverResponse> discoverMovies(
@Query("api_key") String apiKey,
@Nullable @Query("primary_release_date.gte") String fromDate,
@Nullable @Query("primary_release_date.gte") String toDate,
@Nullable @Query("primary_release_year") Integer year,
@Query("language") String language
);Network.java. Comme son nom l'indique, c'est le commencement du path de toutes les requĂȘtes de l'API.
* le path de la requĂȘte "/3/discover/movie" commence par /3 pour choisir la version n°3 de l'API qui permet de s'authentifier via une clĂ© d'API
* Il y a diffĂ©rentes façons de communiquer ses paramĂštres de requĂȘte Ă l'API. Ici on envoie la clĂ© d'API, la date minimum de sortie, et la langue du film sous forme de @Query params comme indiquĂ© par la doc du endpoint.
* Comme type de retour nous avons indiquĂ© Call<RestDiscoverResponse>. RestDiscoverResponse est le modĂšle qu'on reçoit du serveur. On a besoin de dĂ©finir les modĂšles qu'on Ă©change avec l'API parce que structurellement parlant, ils ne correspondent pas forcĂ©ment aux entitĂ©s qu'on manipule dans le code, d'oĂč la dĂ©finition d'un modĂšle RestMovie par exemple. Dans cette classe, nous avons fait appel Ă l'annotation @SerializedName(...) pour indiquer l'appellation du champ dans le Json renvoyĂ© par l'API, et qui peut diffĂ©rer de la façon dont on a appelĂ© notre champ dans le code.ComplĂ©ter le repository
git stash -a && git checkout step4.1Le MovieRepository qu'on a actuellement nous renvoie des "mocks", c'est Ă dire des donnĂ©es stockĂ©es actuellement. Dans cette partie, on va remplacer ces mocks par des films rĂ©cupĂ©rĂ©s via l'API. Pour ce faire, le repository va maintenant appeler les mĂ©thodes qu'on vient de dĂ©finir dans l'API. Comme vous pouvez le constater, ces mĂ©thodes retournent une variable de type Call<type du rĂ©sultat>. C'est seulement en appelant la mĂ©thode execute() de cette variable de type Call que la requĂȘte est rĂ©ellement envoyĂ©e et si aucune exception I/O n'est levĂ©e, on rĂ©cupĂšre un objet de type Response<type du rĂ©sultat>. Cet objet a une mĂ©thode isSuccessful() qui nous permet de savoir si le serveur a rĂ©ussi Ă bien interprĂ©ter notre requĂȘte et nous a renvoyĂ© une rĂ©ponse 200. Dans ce cas on peut rĂ©cupĂ©rer le contenu de la rĂ©ponse via la mĂ©thode body() de l'objet Response qu'on a reçu.
MovieRepository, utiliser la méthode discoverMovies de MovieApi avec les bons paramÚtres dans les méthodes getUpcomingMovies, getTopMoviesOfTheYear et getMoviesInTheatre .
2. Décommenter le dispatchSuccess dans la méthode fetchMovieList
3. Adapter le code de DiscoverPresenter pour qu'au lancement il demande au repository de chercher les films qui sont dans les salles de cinéma en ce moment.Résultat
Si tout s'est bien passĂ©, vous aurez une longue liste de films renvoyĂ©e par le serveur ! S'il y a un problĂšme quelconque, c'est probablement dĂ» Ă une erreur serveur ou Ă une requĂȘte mal formatĂ©e. Pour vous faciliter l'identification de la cause des Ă©ventuels problĂšmes, l'intercepteur graphique de requĂȘtes HTTP Chuck a Ă©tĂ© attachĂ© Ă Retrofit (d'oĂč les 2 petites flĂšches en haut Ă gauche de l'Ă©cran). Cet outil vous permettra de voir ce que vous envoyer Ă l'API et ce qu'elle vous renvoie.

Compléter le layout avec les 3 onglets
git stash -a && git checkout step4.2Le but de cette partie est de rajouter ces 3 onglets pour pouvoir naviguer entre les différentes listes :

Pour cela, on va utiliser un TabLayout.
activity_discover.xml, rajouter un TabLayout en ajustant bien les contraintes pour qu'il apparaisse en haut de l'écran.
(on pourra par exemple mettre les vues actuellement dans un frame layout puis mettre ce frame layout sous les tabs Ă l'aide d'un linear layout)
2. Rajouter 3 TabItem dans le TabLayout de maniĂšre Ă afficher 3 onglets. Pour attribuer Ă chaque onglet son titre, utiliser l'attribut android:text
3. Le titre de l'onglet du milieu est dynamique puisqu'il contient l'année en cours. Il faut donc lui donner une valeur "programmatiquement" (dans le code Java). Pour cela récupérer la vue correspondant au TabLayout dans DiscoverActivity, et récupérer ensuite l'onglet du milieu via la méthode getTabAt de la classe TabLayout. Enfin, utiliser une ressource de type string avec un argument. Pour ce faire, jeter un coup d'oeil à la section Formatting strings.LinearLayout, layout_weight permet de donner un poids aux vues afin qu'elle prenne le plus de poids possible. Gestion des clicks sur les onglets
git stash -a && git checkout step4.3DiscoverActivity, utiliser la méthode addOnTabSelectedListener de la classe TabLayout pour définir un listener sur vos onglets. Ce listener est une interface que l'on doit implémenter. Laissez-vous guider par les indications et les raccourcis d'Android Studio pour générer le code minimum de l'implémentation de cette interface.
2. La mĂ©thode qui nous intĂ©resse le plus est onTabSelected, on peut laisser les 2 autres mĂ©thodes vides. onTabSelected prend en paramĂštre l'onglet qui vient d'ĂȘtre cliquĂ©. RĂ©cupĂ©rer l'indice de cet onglet via getPosition() et en fonction de cet indice, appeler la bonne mĂ©thode du presenter pour dĂ©clencher la rĂ©cupĂ©ration de la bonne liste de films.Bonus
5. Ajout d'un film en favori đ
âČSi tu as pris du retard:git stash -a && git checkout step5.06. Affichage de la liste des favoris đ



