Table des matières
une façon moderne de gérer les listes dans Android à l’aide de DataBinding
Pour commencer, créons un nouveau data
qui sera le seul format utilisé par notre futur adaptateur universel:
data class RecyclerItem(
val data: Any,
@LayoutRes val layoutId: Int,
val variableId: Int
) {
fun bind(binding: ViewDataBinding) {
binding.setVariable(variableId, viewState)
}
}
- Les données détiendra… vos données. Il peut également être traité comme un «modèle d’affichage d’un élément de liste individuel». Cela peut être quelque chose d’aussi simple qu’un POJO simple ou quelque chose de plus complexe avec une logique métier réelle, notre adaptateur ne s’en souciera pas et c’est à vous de décider en fonction du cas d’utilisation.
- layoutId – il s’agit du fichier XML contenant la disposition des lignes de la liste. C’est là que notre adaptateur va essayer de trouver variableId pour l’initialiser avec la valeur de Les données. Il s’agit essentiellement d’une référence à une disposition qui sera utilisée pour rendre Les données.
- variableId contient le nom de la variable qui sera utilisée dans votre mise en page XML à l’intérieur étiquette. Nous en discuterons plus en détail ci-dessous. Consultez également le Liaison de données: variables dynamiques docs pour plus de détails.
Il est maintenant temps d’implémenter l’adaptateur lui-même:
class RecyclerViewAdapter : RecyclerView.Adapter() {
private val items = mutableListOf()override fun getItemCount(): Int {
return items.size
}
Voilà, la version MVP de notre adaptateur universel est prête. Voyons rapidement ce qui se passe ici.
-
getItemViewType
: comme vous le savez probablement, ce rappel est généralement utilisé pour prendre en charge plusieurs types de vues au sein d’une seuleRecyclerView
& adaptateur. Nous revenons simplementlayoutId
de notreRecyclerViewItem
et cette action triviale permet à notre adaptateur de gérer automatiquement différents types de vues. Nous utilisons simplement l’ID de la présentation XML générée comme identifiant de « type de vue ». -
onCreateViewHolder
: l’essentiel de la magie de la liaison de données se produit ici. Nous utilisons DataBindingUtil pour créer dynamiquement un nouveau ViewDataBinding basé sur l’ID de mise en page XML. Tous les XML activés pour la liaison de données ont une classe générée dérivée de cette classe de base. Nous ne connaissons pas le nom de cette classe ici, mais nous savons qu’il sera généré pour lalayoutId
et il hériteraViewDataBinding
. -
onBindViewHolder
: ici nous devons lier notreViewDataBinding
avecviewModel
. Chaque classe de liaison de données a leserVariable()
méthode qui accepte l’ID variable et les données deObject
type. Vous l’aurez deviné, ce sont nosvariableId
etviewModel
duRecyclerViewItem
.
Comme nous l’avons mentionné précédemment, nous aimerions éviter d’écrire du code de collage dans nos fragments et activités, la dernière étape consiste donc à créer un BindingAdapter
ce qui peut nous aider à tout faire avec seulement 1 ligne de code en XML de fragment ou d’activité:
@BindingAdapter("items")
fun setRecyclerViewItems(
recyclerView: RecyclerView,
items: List?
) {
var adapter = (recyclerView.adapter as? RecyclerViewAdapter)
if (adapter == null) {
adapter = RecyclerViewAdapter()
recyclerView.adapter = adapter
}adapter.updateData(items.orEmpty())
}
Il est maintenant temps de voir comment tout cela fonctionne ensemble dans la pratique. Imaginons que nous ayons besoin d’implémenter un écran affichant une liste d’utilisateurs. Voici à quoi pourrait ressembler votre classe POJO:
data class User(
val id: Int,
val firstName,
val lastname
)
Créons maintenant une mise en page qui sera utilisée pour des lignes individuelles:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
name="user"
type="com.package.User" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
layout>
Rien de nouveau ici pour ceux qui connaissent le cadre de liaison de données. Revenons maintenant à notre modèle de vue: nous devons toujours récupérer les utilisateurs, puis les «mettre» dans une RecyclerView d’une manière ou d’une autre.
class MyViewModel : ViewModel() {val data = MutableLiveData()
init {
loadData()
}
private fun loadData() {
SomeService.getUsers { users ->
data.value = users.map { it.toRecyclerItem() }
}
}
}
Comme vous le voyez, nous avons maintenant List
au lieu de quelque chose comme List
et nous utilisons user.toRecyclerItem()
méthode pour convertir nos classes de données en quelque chose qui peut être reconnu par RecyclerView
– RecyclerViewItem
. Nous ne voulons pas gâcher nos POJO avec une logique qui appartient à une autre couche d’abstraction (UI), alors créons plutôt une extension privée dans le fichier contenant le ViewModel
:
private fun User.toRecyclerItem() = RecyclerViewItem(
viewModel = this,
variableId = BR.user,
layoutId = R.layout.item_user
)
C’est le seul code «glue» que nous devons écrire afin de combler l’écart entre le format de données naturelles de ViewModel (liste des utilisateurs) et RecyclerView
. La dernière étape que nous devons faire est de rendre le List
dans notre interface utilisateur. Merci à DataBinding et au BindingAdapter
nous avons créé plus tôt, tout cela peut être fait en XML:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
name="viewModel"
type="com.package.MyViewModel" />
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.data}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
La dernière étape consiste à attribuer votre ViewModel
à la viewModel
variable d’en haut:
class MyFragment : Fragment() {val viewModel: MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return FragmentMyScreenBinding.inflate(inflater, container, false)
.also {
it.viewModel = viewModel
it.lifecycleOwner = viewLifecycleOwner
}
.root
}
}
C’est tout et c’est le seul code que vous devez écrire dans vos activités ou fragments lorsque vous utilisez DataBinding et l’approche «sans adaptateur» dont nous discutons dans cet article. Assez sympa, hein? 🙂
Revoyons ce que nous avons fait jusqu’à présent pour vérifier si elle est meilleure que la version originale avec « 1 adaptateur pour 1 liste ».
- Nous n’avons plus besoin de créer de nouveaux adaptateurs. Déjà. Avec des ajouts mineurs, ce
RecyclerViewAdapter
peut être le seul adaptateur de votre projet. - Nos fragments et activités n’ont plus à se soucier des adaptateurs et de l’observation des données. La seule chose qu’ils doivent faire est d’attribuer un
viewModel
à la disposition XML correspondante et cela semble et semble assez naturel. Tout le reste est géré en XML avec seulement quelques lignes de code. - Nous n’avons plus besoin d’exposer le format de données de domaine des modèles de vue aux activités et aux fragments. Au lieu de cela, il vous suffit de fournir une liste générique de
RecyclerViewItem
s qui masque le format de données réel de l’interface utilisateur etRecyclerViewItem
peut être manipulé directement par l’interface utilisateur sans transformations supplémentaires (grâce à laDataBindingAdapter
etRecyclerViewAdapter
il utilise sous le capot). - Vous n’avez pas besoin de gâcher vos POJO ou votre élément de recyclage Afficher les modèles en étendant une classe de base, vous n’avez même pas besoin d’implémenter d’interface supplémentaire. Les POJO ou modèles d’affichage que vous utilisez pour
RecyclerView
ressemblent à des classes ordinaires. Vous avez juste besoin d’écrire un mappeur quelque part où cela vous semble le plus logique (fonction statique ou d’extension renvoyant unRecyclerViewItem
). - Il n’est pas nécessaire d’étendre l’adaptateur à chaque fois qu’un nouveau type d’élément est ajouté. Vous pouvez facilement prendre en charge autant de types de vues dans une seule liste que vous le souhaitez, il vous suffit de définir des mappages de vos données vers
RecyclerViewItem
:
val data = MutableLiveData()