Creación de una aplicación de noticias utilizando el protocolo QUIC [Kotlin]
Tags: android, huaweimobileservices, hmscore, huawei
Published: 2020-09-17 17:24:04

QUIC es un nuevo protocolo de transporte basado en UDP que permite mejorar la velocidad de las conexiones de red debido a la baja latencia de UDP. QUIC significa Quick UDP Internet Connections y está previsto que se convierta en un estándar.
Las características clave de QUIC son:
* Tiempo de establecimiento de conexión drásticamente reducido
* Control de congestión mejorado
* Multiplexación sin bloqueo de cabecera de línea
* Migración de conexión

Algunas empresas están comenzando a admitir QUIC en sus servidores, ahora desde el lado del cliente, es hora de estar preparados y agregar soporte QUIC a nuestras aplicaciones. Esta vez construiremos una aplicación de noticias con soporte QUIC usando el kit HQUIC.
HQUIC nos permite conectarnos con servicios web a través del protocolo QUIC, si el control remoto no es compatible con QUIC, el kit usará HTTP 2 en su lugar automáticamente.

#Requisitos previos

* Un proyecto de Android Studio
* Una cuenta de desarrollador en newsapi.org

# Agregar las dependencias requeridas
Este ejemplo requerirá las siguientes dependencias:
* RecyclerView: Para mostrar todas las novedades en una lista.
* SwipeRefreshLayout: Para permitir que el usuario actualice la pantalla con un gesto de deslizar.
* HQUIC:El kit que nos permite conectarnos con servicios a través de QUIC

Para usar HQUIC debes agregar el repositorio de Huawei a su nivel superior build.gradle.
```
buildscript {
    buildscript {
        repositories {
            maven { url 'https://developer.huawei.com/repo/' }// This line
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.3.2'
        }
    }
}
allprojects {
    repositories {
        maven { url 'https://developer.huawei.com/repo/' }// and this one
        google()
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
```
Ahora agregue las dependencias requeridas a nivel de aplicación build.gradle
```
implementation 'com.huawei.hms:hquic-provider:5.0.0.300'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
```
# Diseñando la UI

Mostraremos una lista de elementos dentro de un RecyclerView, por lo que debes preparar el diseño del elemento que se mostrará para cada artículo. Esta vista básica solo mostrará el título, una breve introducción y la fecha de publicación:
```
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">
<TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:text="TextView"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
<TextView
        android:id="@+id/content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:text="Content"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
        android:id="@+id/time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/content" />
</androidx.constraintlayout.widget.ConstraintLayout>
```
Para el diseño de la actividad, el diseño contendrá un SwipeRefresLayout con el RecyclerView dentro

```
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:id="@+id/swipeRefreshLayout">
        
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerNews"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
```
Para mostrar los artículos en una vista de reciclador, necesitarás un adaptador, el adaptador informará el evento de clic de cualquier elemento al oyente dado.

```
class NewsAdapter(val news:ArrayList<Article>,var listener: NewsViewHolder.onNewsClickListener?): RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
class NewsViewHolder(itemView: View, var listener:onNewsClickListener?) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
        public fun init(article: Article){
            itemView.title.text=article.title
            itemView.content.text=article.description
            val date= SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()).parse(article.time)
            itemView.time.text=date?.toString()
            itemView.setOnClickListener(this)
        }
interface onNewsClickListener{
            fun onClickedArticle(position: Int)
        }
override fun onClick(v: View?) {
            listener?.onClickedArticle(adapterPosition)
        }
    }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
        val view=LayoutInflater.from(parent.context)
            .inflate(R.layout.item_view,parent,false)
        return NewsViewHolder(view,listener)
    }
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        holder.init(news[position])
    }
override fun getItemCount(): Int {
        return news.size
    }
}
```
# HQUIC Service
Usaremos la clase HQUICService proporcionada por el proyecto de demostración HQUIC. Esta clase abstrae las capacidades del kit HQUIC y nos permite realizar una solicitud fácilmente.

```
class HQUICService (val context: Context){
private val TAG = "HQUICService"
private val DEFAULT_PORT = 443
private val DEFAULT_ALTERNATEPORT = 443
private val executor: Executor = Executors.newSingleThreadExecutor()
private var cronetEngine: CronetEngine? = null
private var callback: UrlRequest.Callback? = null


/**
     * Asynchronous initialization.
     */
    init {
        HQUICManager.asyncInit(
            context,
            object : HQUICManager.HQUICInitCallback {
                override fun onSuccess() {
                    Log.i(TAG, "HQUICManager asyncInit success")
                }
override fun onFail(e: Exception?) {
                    Log.w(TAG, "HQUICManager asyncInit fail")
                }
            })
    }
/**
     * Create a Cronet engine.
     *
     * @param url URL.
     * @return cronetEngine Cronet engine.
     */
    private fun createCronetEngine(url: String): CronetEngine? {
        if (cronetEngine != null) {
            return cronetEngine
        }
        val builder= CronetEngine.Builder(context)
        builder.enableQuic(true)
        builder.addQuicHint(getHost(url), DEFAULT_PORT, DEFAULT_ALTERNATEPORT)
        cronetEngine = builder.build()
        return cronetEngine
    }
/**
     * Construct a request
     *
     * @param url Request URL.
     * @param method method Method type.
     * @return UrlRequest urlrequest instance.
     */
    private fun builRequest(url: String, method: String): UrlRequest? {
        val cronetEngine: CronetEngine? = createCronetEngine(url)
        val requestBuilder= cronetEngine?.newUrlRequestBuilder(url, callback, executor)
        requestBuilder?.apply {
            setHttpMethod(method)
            return build()
        }
        return null
    }
/**
     * Send a request to the URL.
     *
     * @param url Request URL.
     * @param method Request method type.
     */
    fun sendRequest(url: String, method: String) {
        Log.i(TAG, "callURL: url is " + url + "and method is " + method)
        val urlRequest: UrlRequest? = builRequest(url, method)
        urlRequest?.apply { urlRequest.start() }
    }
/**
     * Parse the domain name to obtain the host name.
     *
     * @param url Request URL.
     * @return host Host name.
     */
    private fun getHost(url: String): String? {
        var host: String? = null
        try {
            val url1 = URL(url)
            host = url1.host
        } catch (e: MalformedURLException) {
            Log.e(TAG, "getHost: ", e)
        }
        return host
    }
fun setCallback(mCallback: UrlRequest.Callback?) {
        callback = mCallback
    }
}
```
# Descargando las noticias

Definiremos una clase auxiliar para manejar la solicitud y analiza la respuesta en una ArrayList. El kit HQUIC leerá una cierta cantidad de bytes por vez, para respuestas grandes, el método onReadCompleted se llamará más de una vez.
```
data class Article(val author:String,
                   val title:String,
                   val description:String,
                   val url:String,
                   val time:String)
class NewsClient(context: Context): UrlRequest.Callback() {
    var hquicService: HQUICService? = null
    val CAPACITY = 10240
    val TAG="NewsDownloader"
    var response:StringBuilder=java.lang.StringBuilder()
    var listener:NewsClientListener?=null
init {
        hquicService = HQUICService(context)
        hquicService?.setCallback(this)
    }
fun getNews(url: String, method:String){
        hquicService?.sendRequest(url,method)
    }
override fun onRedirectReceived(
        request: UrlRequest,
        info: UrlResponseInfo,
        newLocationUrl: String
    ) {
        request.followRedirect()
    }
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
        Log.i(TAG, "onResponseStarted: ")
        val byteBuffer = ByteBuffer.allocateDirect(CAPACITY)
        request.read(byteBuffer)
    }
override fun onReadCompleted(
        request: UrlRequest,
        info: UrlResponseInfo,
        byteBuffer: ByteBuffer
    ) {
        Log.i(TAG, "onReadCompleted: method is called")
        val readed=String(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.position())
        response.append(readed)
        request.read(ByteBuffer.allocateDirect(CAPACITY))
    }
override fun onSucceeded(request: UrlRequest?, info: UrlResponseInfo?) {
        //If everything is ok you can read the response body
        val json=JSONObject(response.toString())
        val array=json.getJSONArray("articles")
        val list=ArrayList<Article>()
        for (i in 0 until array.length()){
            val article=array.getJSONObject(i)
            val author=article.getString("author")
            val title=article.getString("title")
            val description=article.getString("description")
            val time=article.getString("publishedAt")
            val url=article.getString("url")
            list.add(Article(author, title, description, url, time))
        }
        listener?.onSuccess(list)
    }
override fun onFailed(request: UrlRequest, info: UrlResponseInfo, error: CronetException) {
        //If someting fails you must report the error
        listener?.onFailure(error.toString())
    }
public interface NewsClientListener{
        fun onSuccess(news:ArrayList<Article>)
        fun onFailure(error: String)
    }
}
```
# Haciendo la solicitud
Definir propiedades de solicitud.


```
private val API_KEY="YOUR_API_KEY"
private val URL = "https://newsapi.org/v2/top-headlines?apiKey=$API_KEY"
private val METHOD = "GET"
```

Llama a la función getNews para iniciar la solicitud, si todo va bien, la lista de noticias se entregará en la devolución de llamada onSuccess. Si ocurre un error, se activará la devolución de llamada onFailure.

```
private fun getNews() {
    val country=Locale.getDefault().country
    val url= "$URL&country=$country"
    Log.e("URL",url)
    val downloader=NewsClient(this)
    downloader.apply {
        listener=this@MainActivity
        getNews(url,METHOD)
    }
}
override fun onSuccess(news: ArrayList<Article>) {
    this.news.apply {
        clear()
        addAll(news)
    }
    runOnUiThread{
        swipeRefreshLayout.isRefreshing=false
        loadingDialog?.dismiss()
        adapter?.notifyDataSetChanged()
    }
}
override fun onFailure(error: String) {
    val errorDialog=AlertDialog.Builder(this).apply {
        setTitle(R.string.error_title)
        val message="${getString(R.string.error_message)} \n $error"
        setMessage(message)
        setPositiveButton(R.string.ok){ dialog, _ ->
            dialog.dismiss()
        }
        setCancelable(false)
        create()
        show()
    }
}
```
# Conclusión
Es solo cuestión de tiempo para que el protocolo QUIC se convierta en el nuevo estándar de conexiones a Internet. Por ahora, puedes preparar tus aplicaciones para admitirlo y brindar la mejor experiencia de usuario.
Prueba el demo: https://github.com/danms07/HQUICNews

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/8d8d332bhhj6jck5lmob.png)

Les compartimos el link al articulo original
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202352465220930189&fid=0101187876626530001