• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 02/02/2022

Définissez une structure de données avec des entités

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Maintenant que Room (notre ORM) est installé, nous allons pouvoir commencer à représenter nos données sous forme de modèles (via des simples classes POJO). Le but sera de modéliser la fonctionnalité "liste de choses à faire" de notre écran TodoListActivity.

Pour y voir un peu plus clair, reprenons cet écran afin de mieux visualiser nos besoins et surtout les modèles que nous devrons créer.

Une modélisation possible de l'application : Une class User avec une photo et le nom de Philippe, une Classe Item, comme
Exemple de modélisation

Dans notre cas, j'ai choisi de modéliser cette fonctionnalité de la manière suivante :

  • La classe   User  : représentera l'utilisateur actuellement connecté à notre application. Ce dernier possèdera un nom et une photo de profil.

  • La classe   Item  : représentera une chose à faire. Elle aura un titre ainsi qu'une catégorie et aura la possibilité d'être marquée comme étant "Faite". Elle gardera également en mémoire l'utilisateur qui l'aura créée.

Maintenant, créons les classes correspondantes en Java. :) Pour cela, créez un nouveau package appelé models/ puis ajoutez-y les classes User.java et Item.java.

Classe User.java :

public class User {
private long id;
private String username;
private String urlPicture;
public User(long id, String username, String urlPicture) {
this.id = id;
this.username = username;
this.urlPicture = urlPicture;
}
// --- GETTER ---
public long getId() { return id; }
public String getUsername() { return username; }
public String getUrlPicture() { return urlPicture; }
// --- SETTER ---
public void setId(long id) { this.id = id; }
public void setUsername(String username) { this.username = username; }
public void setUrlPicture(String urlPicture) { this.urlPicture = urlPicture; }
}

Classe Item.java :

public class Item {
private long id;
private String text;
private int category;
private Boolean isSelected;
private long userId;
public Item() { }
public Item(String text, int category, long userId) {
this.text = text;
this.category = category;
this.userId = userId;
this.isSelected = false;
}
// --- GETTER ---
public long getId() { return id; }
public String getText() { return text; }
public int getCategory() { return category; }
public Boolean getSelected() { return isSelected; }
public long getUserId() { return userId; }
// --- SETTER ---
public void setId(long id) { this.id = id; }
public void setText(String text) { this.text = text; }
public void setCategory(int category) { this.category = category; }
public void setSelected(Boolean selected) { isSelected = selected; }
public void setUserId(long userId) { this.userId = userId; }
}

Explications : Nous avons créé pour le moment ces deux objets qui semblent assez basiques. Nous leur avons ajouté à chacun un constructeur et des getters/setters.

Effectivement, ces classes modèles sont assez simples, mais comment faire pour les sauvegarder en base de données ? :euh:

Pour nous aider, j'ai créé dans un premier temps un Modèle Physique de Données (MPD) afin de mieux représenter les relations entre ces objets, qui deviendront par la suite... des tables ! :)

La classe User contient un username: String, un urlPicture: String et un id: long (PK). Ce dernier est connecté à userID: long (FK) dans la classe Item contenant aussi un id: long (PK), un text: String, une category: int, un isSelected: Boolean.
Le modèle physique de données pour notre application SaveMyTrip

Nous avons ici deux tables, correspondant à nos deux précédents modèles :

  • la table User

    • clé primaire (PK) : le champ  id  représentant l'identifiant unique pour notre utilisateur ;

  • la table   Item  :

    • clé primaire (PK) : le champ  id  représentant l'identifiant unique d'une "chose à faire" ;

    • clé étrangère (FK) : le champ  userId  représentant l'utilisateur qui a créé la "chose à faire". 

Maintenant que nous avons une représentation un peu plus avancée de la structure de notre base de données, nous allons modifier nos deux classes de modèles afin de les définir comme étant des tables aux yeux de Room.

Extrait de User.java :

@Entity
public class User {
@PrimaryKey
private long id;
}

Explications : Pour notre plus grand plaisir, Room nous propose énormément d'annotations afin de faciliter sa configuration. Ainsi, nous avons défini ici notre classe "User" comme étant une table grâce à l'annotation @Entity.

Une table devant posséder au moins une clé primaire, nous définissons la propriété  id  comme étant la clé primaire de la table "User" grâce à l'annotation @PrimaryKey. Rapide et efficace, non ? :)

Extrait de Item.java :

@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "userId"))
public class Item {
@PrimaryKey(autoGenerate = true)
private long id;
private long userId;
}

Explications :  La table "Item" est un peu différente, car nous avons ajouté, à l'intérieur de l'annotation  @Entity  , la relation clé-étrangère/clé-primaire grâce à l'annotation @ForeignKey.

Nous aurions pu aussi utiliser les classes@Embeddedpour inclure la classe  User  directement dans  Item  sans se soucier des clés étrangères. La classe  Item  ressemblerait à :

@Entity
public class Item {
@Embedded(prefix = "user_")
private User user;

 Cette annotation  va ajouter automatiquement les champs de notre classe  User  dans  Item  et ainsi créer une relation. À noter que nous devons ajouter le paramètre  prefix  , car sinon notre table Item aurait 2 champs nommés  id  , celui de  Item  et celui de  User  . Autrement dit, avec cette annotation, nous avons ajouté les champs de  User  préfixé par “user_”, donc user_id, user_username et user_urlPicture. 

Vous l'avez peut-être remarqué, mais nous avons ajouté le paramètreautoGenerate = trueà l'annotation @PrimaryKey : cela permettra à Room de générer automatiquement un identifiant unique pour chaque item sauvegardé. ;)

Une bonne chose de faite ! Maintenant, je vais vous donner simplement le contenu de l'Adapter et du ViewHolder que nous utiliserons par la suite à travers la RecyclerView de l'activité TodoListActivity. Et vous vous en doutez, la RecyclerView contiendra une liste... d'Item ! :D

Placez ces classes dans le package todolist/.

Classe todolist/ItemViewHolder.java : 

public class ItemViewHolder extends RecyclerView.ViewHolder {
private final ActivityTodoListItemBinding binding;
public ItemViewHolder(ActivityTodoListItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void updateWithItem(Item item, ItemAdapter.Listener callback) {
binding.getRoot().setOnClickListener(view -> callback.onItemClick(item));
binding.activityTodoListItemRemove.setOnClickListener(view -> callback.onClickDeleteButton(item));
binding.activityTodoListItemText.setText(item.getText());
switch (item.getCategory()) {
case 0: // TO VISIT
binding.activityTodoListItemImage.setBackgroundResource(R.drawable.ic_room_black_24px);
break;
case 1: // IDEAS
binding.activityTodoListItemImage.setBackgroundResource(R.drawable.ic_lightbulb_outline_black_24px);
break;
case 2: // RESTAURANTS
binding.activityTodoListItemImage.setBackgroundResource(R.drawable.ic_local_cafe_black_24px);
break;
}
if (item.getSelected()) {
binding.activityTodoListItemText.setPaintFlags(binding.activityTodoListItemText.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
binding.activityTodoListItemText.setPaintFlags(binding.activityTodoListItemText.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
}
}
}

Explications : La classe représentant chaque ligne de la RecyclerView. Je ne l'explique pas plus que ça, car vous devez normalement comprendre assez facilement l'essentiel du code.

Classe todolist/ItemAdapter.java : 

public class ItemAdapter extends RecyclerView.Adapter<ItemViewHolder> {
// CALLBACK
public interface Listener {
void onClickDeleteButton(Item item);
void onItemClick(Item item);
}
private final Listener callback;
// FOR DATA
private List<Item> items;
// CONSTRUCTOR
public ItemAdapter(Listener callback) {
this.items = new ArrayList<>();
this.callback = callback;
}
@Override
@NotNull
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
return new ItemViewHolder(ActivityTodoListItemBinding.inflate(inflater, parent, false));
}
@Override
public void onBindViewHolder(ItemViewHolder viewHolder, int position) {
viewHolder.updateWithItem(this.items.get(position), this.callback);
}
@Override
public int getItemCount() {
return this.items.size();
}
public void updateData(List<Item> items){
this.items = items;
this.notifyDataSetChanged();
}
}

Explications : Cette classe représente l'Adapter faisant le lien entre la RecyclerView et le ViewHolder. Je ne vais également pas plus l'expliquer que ça, car celle-ci reste assez compréhensible en soi. Si vous avez des difficultés à assimiler le fonctionnement du callback et de son interface, n'hésitez pas à relire le chapitre de ce cours.

En résumé

  • Pour définir qu’une classe est une table, on l’annote avec  @Entity  .

  • On peut gérer des relations entre classe soit avec l’annotation  @ForeignKey  soit avec  @Embedded  , la différence étant que cette dernière intégrera les champs de la table relationnelle.

Nos entités sont prêtes à être manipulées ! Nous allons voir dès le prochain chapitre comment créer nos premières requêtes SQL sur notre base de données SQLite.

Exemple de certificat de réussite
Exemple de certificat de réussite