Hoy en día podemos decir que todas las aplicaciones para móviles se comunican con al menos un servidor para recoger datos y mostrarlos al usuario. Necesitamos crear depósitos de datos robustos para proporcionar aplicaciones rápidas y resistentes que puedan superar muchas situaciones malas como un mal servicio de Internet, cambios constantes de datos móviles a redes wifi, o backends saturados que pueden no responder en el tiempo esperado.

¿Qué es la resistencia?

La resistencia es la capacidad de manejar fallas parciales mientras se continúa ejecutando y no chocando. Por ejemplo, las aplicaciones que se comunican a través de redes (como los servicios que hablan con una base de datos o una API) están sujetas a fallos transitorios. Estas fallas transitorias causan problemas en las redes y otros problemas que van y vienen y son difíciles de reproducir.

Retry Pattern usando RxSwift

El retry pattern es uno de los mecanismos más fáciles y efectivos para tratar esos problemas transitorios que pueden afectar a nuestras aplicaciones móviles. Construir este tipo de mecanismos de manejo de errores puede ser difícil y propenso a errores. Usando RxSwift tienes un método que automáticamente hace eso por ti.

.retry(maxAttemptCount: Int)

Este método se basa en el operador de retry descrito en la documentación de ReactiveX: http://reactivex.io/documentation/operators/retry.html


“El operador del reintento responde a una notificación de error de la fuente observable no pasando esa llamada a sus observadores, sino volviéndose a suscribir a la fuente observable y dándole otra oportunidad de completar su secuencia sin error. “

self.apiClient.someApiCall()
            .retry(5)

Este método puede ser usado en un ObservableType (Single<T>, Observable<T>, y otros…) e inmediatamente re-suscribirá la secuencia de la fuente si recibe un evento onError disparando la secuencia de nuevo. En el ejemplo, someApiCall() devuelve una secuencia observable con la respuesta de una llamada a la API.

 

Esto nos permitirá reintentar la solicitud si falla, pero como esto activará la solicitud al fallar, puede que no sea lo ideal si el dispositivo no puede obtener servicio rápidamente o el servidor está momentáneamente sobrecargado. En esas situaciones, querríamos esperar unos segundos después de reintentar la petición.

Gracias a la comunidad RxSwift tenemos un proyecto llamado RxSwiftExt que tiene muchos métodos convenientes para hacer este tipo de tarea: https://github.com/RxSwiftCommunity/RxSwiftExt

 

Esta extensión tiene varios tipos de mecanismo de reintento, se llama RepeatBehaviour y tiene:

  • .immediate: la misma que la de RxSwift.
  • .delayed:  aplica un retardo entre reintentos.
  • .exponentialDelayed: igual que retrasado pero el tiempo de retraso se incrementará exponencialmente entre reintento.
  • .customTimerDelayed: podemos proporcionar nuestro temporizador de retardo personalizado.
self.apiClient.someApiCall()
        .retry(.exponentialDelayed(maxCount: 5, initial: 1.0, multiplier: 2.0))

Así que si cambiamos el ejemplo para usar RepeatBehaviour.exponentialDelayed el primer reintento se retrasará 1 segundo, el segundo reintento 2 segundos, el tercero 4 segundos, y así sucesivamente. Si no recibimos ningún evento de éxito después de cinco reintentos, el operador de reintento provocará un evento de error en la secuencia subyacente.

Añadiendo un tiempo de espera personalizado

Si por alguna razón queremos establecer un tiempo de espera personalizado para una solicitud determinada podemos hacer uso del operador de tiempo de espera. Este operador emite un evento de error de timeout si la secuencia fuente no emite ningún evento en el tiempo dado.

.timeout(dueTime: RxTimeInterval, scheduler: SchedulerType)

Podemos combinar este operador con el reintento de la siguiente manera:

self.apiClient.someApiCall()
          .timeout(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
          .retry(5)

Conclusiones

Como se ha visto en este artículo, es realmente fácil aplicar una estrategia de reintento en sus repositorios utilizando los métodos .retry() y .timeout(). Esto hará que su aplicación sea más resistente a esos fallos transitorios que son muy comunes en las aplicaciones móviles. Podemos aplicar estas estrategias a las consultas de bases de datos, solicitudes de red y todos los demás tipos de repositorios que su aplicación pueda tener para interactuar con otros servicios. Estos métodos de conveniencia están presentes en casi todas las bibliotecas reactivas en móviles como Combine, RxSwift, RxJava, ¡así que te animo a que empieces a usarlos para crear mejores aplicaciones!