Construyendo una App cliente de noticias con el protocolo QUIC, MVVM y Data Binding [parte 3]

Daniel Mendoza
Huawei Developers Latinoamérica
3 min readOct 28, 2020
Blog writer Illustration by Delesign Graphics

Llegamos a la parte final de esta serie, es el momento de llenar nuestro RecyclerView con las noticias descargadas de newsapi.org.

Construyendo el Adaptador

El adaptador se encargará de gestionar las vistas en pantalla para cada una de las noticias que tengamos en nuestra lista.

NewsViewHolder

Lo primero que debemos hacer es definir nuestro ViewHolder, que se encargará de asociar un elemento de la lista con una vista.

class NewsViewHolder(private val itemBinding: ArticleBinding) : RecyclerView.ViewHolder(itemBinding.root){
public fun bind(article: Article, newsVM: NewsViewModel){
itemBinding.article=article
itemBinding.mainVM=newsVM
}
}

Como verás, nuestro ViewHolder recibe una instancia de una clase llamada ArticleBinding. Esta clase es generada por la librería de Data Binding gracias a la configuración que hicimos previamente en el layout item_view.xml

<data class="ArticleBinding">
<variable
name="article"
type="com.hms.demo.hquicnews.Article" />

<variable
name="mainVM"
type="com.hms.demo.hquicnews.NewsViewModel" />
</data>

De esta forma en el método bind del ViewHolder le indicamos al ArticleBinding la instancia de Article para actualizar el texto desplegado en sus TextView y el ViewModel al que le reportará los clics que el usuario haga sobre la vista.

ArticleBinding se encargará por nosotros de obtener los datos de Article y mostrarlos en los TextView tal como lo especificamos en el xml, delegando así las tareas de asignación de datos a la librería de Data Binding.

NewsAdapter

Nuestro adaptador recibirá la lista de noticias y el ViewModel con el onClickListener. Los Adapters deben sobreescribir los métodos de RecyclerView.Adapter:

  • onCreateViewHolder: Se ejecutará tantas veces como el número de elementos de nuestra lista. Aquí obtendremos una instancia de ArticleBinding para crear y retornar un NewsViewHolder.
  • onBindViewHolder: Aquí debemos vincular nuestro ViewHolder con la información que va a desplegar.
  • getItemCount: Debe retornar el tamaño de la lista para que el Adapter sepa cuántas veces debe llamar a onCreateViewHolder.
class NewsAdapter(private val news:ArrayList<Article>,private val newsVM:NewsViewModel): RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {

class NewsViewHolder(private val itemBinding: ArticleBinding) : RecyclerView.ViewHolder(itemBinding.root){
public fun bind(article: Article, newsVM: NewsViewModel){
itemBinding.article=article
itemBinding.mainVM=newsVM
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val layoutInflater=LayoutInflater.from(parent.context)
val articleBinding=ArticleBinding.inflate(layoutInflater,parent,false)
return NewsViewHolder(articleBinding)
}

override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
holder.bind(news[position],newsVM)
}

override fun getItemCount(): Int {
return news.size
}

News Activity

Ya tenemos todo listo, ahora sólo nos queda agregar la funcionalidad de nuestro Activity.

Lo primero que haremos será aplicar un Refactor -> Rename al MainActivity que nos generó Android Studio al crear nuestro proyecto (Esto es opcional y no impacta en el funcionamiento de la App).

class NewsActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener, NewsViewModel.NewsViewModelListener{
private var adapter:NewsAdapter?=null
private lateinit var viewModel: NewsViewModel
private lateinit var binding: ActivityNewsBinding
}

Desde el método onCreate haremos lo siguiente

  • El View Bindind de nuestro layout
  • Agregar un onRefreshListener al SwipeRefreshLayout
  • Agregar un observer al LiveData que contiene la lista de noticias
  • Llamar a NewsViewModel.loadNews, el cuál descargará las noticias si la lista está vacía o llamará al método onNewsDownloadComplete si ya había noticias. Esto evita que se vuelvan a descargar si el usuario rota la pantalla.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityNewsBinding.inflate(layoutInflater)
setContentView(binding.root)
this.viewModel = ViewModelProviders.of(this).get(NewsViewModel::class.java)
binding.swipeRefreshLayout.setOnRefreshListener(this)
binding.recyclerNews.layoutManager=LinearLayoutManager(this)

viewModel.news.observe(this){
adapter= NewsAdapter(it,viewModel)
binding.recyclerNews.adapter=adapter
}
binding.swipeRefreshLayout.isRefreshing=true
viewModel.listener=this
viewModel.loadNews(this)
}

En el método onRefresh llamaremos a getNews para forzar la descarga, ya que el usuario estaría refrescando la lista de noticias intencionalmente.

override fun onRefresh() {
viewModel.getNews(this)
}

Por último sólo nos queda sobreescribir los métodos de NewsViewModel.NewsViewModelListener.

En onNewsDownloadComplete, si la descarga se completa u ocurre un problema, el SwipeRefreshLayout dejará de mostrar su ProgressBar.

override fun onNewsDownloadComplete() {
binding.swipeRefreshLayout.isRefreshing=false
}

Desde el método onItemClick podemos decidir qué hacer si el usuario hace clic en alguna de las noticias. En este caso, abriremos la noticia en una pestaña del browser que esté configurado por defecto.

override fun onItemClick(article: Article) {
val intent= Intent(Intent.ACTION_VIEW)
intent.data= Uri.parse(article.url)
startActivity(intent)
}

Y listo! Al compilar y ejecutar la App obtendremos el siguiente resultado:

App en funcionamiento

Eso es todo. Ahora tenemos una app lista para el salto a HTTP/3.

¡Gracias por leer!

--

--