Seguimos nuestro camino en la construcción de una aplicación en iOS con RxSwift. En este artículo, hoy terminaremos de crear la primera funcionalidad en su primera aproximación. Nos queda implementar la capa de Servicio, o Data o Infraestructura.

En la segunda parte de este artículo, implementaremos una paginación. La llamada que busca álbumes, viene paginada. Así que, me parece muy interesante, utilizar esta opción para devolver una tabla con muchos elementos. En marcha.

Vamos a usar el servicio API de discogs. Si no lo has usado nunca, te recomiendo registrarte para usar una api key.

El código de este artículo se encuentra aquí.

https://github.com/agescura/SearchMusic/tree/featured/f4-data-frameworks

Una vez registrada una app, tendremos una key y un secret. Crea una nueva carpeta llamada Data. Crea un nuevo archivo llamado DiscogsService.swift.

Usaremos Moya. En Podfile, añade el pod nuevo.

Nota: Moya no se encuentra ahora mismo actualizado a RxSwift 6. Si tienes algún fallo (yo lo tuve), borra el Podfile.lock y vuelve a instalar. Al intentar compilar tendrás un par de errores. En RxSwift 5 es catchErrorJustReturn.

Con Moya, podemos crear llamadas de una forma sencilla y rápida. En nuestro caso queremos crear una llamada que nos permita hacer una busqueda.

En la documentación de discogs, tenemos la relacionada con hacer una llamada.

En esta primera versión de la búsqueda, escribiremos los siguiente:

El tipo search tendrá album que representará el album a buscar y una página. La página la usaremos para la paginación, pero esto vendrá dentro de poco. Antes irá esto.

Para obtener la información, necesitamos usar el protocolo Codable. Iremos rápido aquí.

Dentro de Data, crea una carpeta llamada DTOs y dentro crea un archivo SearchDTO.swift

Al guardar un valor de respuesta con otro nombre, tenemos que especificarlo usando CodingKeys.

Crea otro archivo llamado AlbumDTO.swift. Si, he usado optionals en aquellos valores que no siempre tienen resultado. It is what it is.

Con esto tendríamos ya casi la llamada hecha, but.

Necesitamos un par de métodos para usar Codable en RxSwift. La fuenta está prestada de aquí, pero algo modificada.

Crea una carpeta Extensions dentro de Data y crea un archivo ObservableType.swift.

Estamos creando varios mappers para codificar las respuestas que vengan de Moya y así obtener nuestros propios objetos.

Unidirectional Flow. El patrón de subscripción y observación viene implícitamente separado en dos flujos. Por un lado, existen unos eventos, que podemos decir de entrada. Por ejemplo, cuando un usuario le hace tap a un botón o cuando un usuario va haciendo scroll. Por otro lado, existen unos eventos de salida. Por ejemplo, cuando el servicio nos da una respuesta con una serie de información.

Ahora mismo, en el viewController, cuando hacemos bind estamos haciendo la subscripción y la observación al mismo tiempo, con lo cual, estamos infringiendo lo que se llama el Unidirectional Flow.

Crea una carpeta nueva llamada Protocols y dentro, crea un archivo llamado TransformType.swift

Con este mecanismo, podremos asociar eventos de entrada y, luego, generarán, eventos de salida. Es una manera sencilla de aplicar el Unidirectional Flow.

Bien, entonces. ¿Qué necesitamos? Necesitamos como entrada el valor que necesitamos para hacer la búsqueda. Y, como resultado, un listado de albums. Vamos allá.

Crea un archivo dentro de Data, llamado DiscogsRepository. Instanciamos el servicio de Moya y le pasamos el enum que creamos antes.

Crea una extension de la siguiente forma. Dentro de inputs, tendremos la búsqueda y dentro de outputs, el resultado de la búsqueda.

Necesitamos un método privado, que realice la búsqueda, como tal.

Hacemos la llamada con Moya y usamos nuestra extension para obtener el resultado del DTO y luego, volvemos a transformar el resultado al tipo de objeto que quiere Dominio, esto es, un array de Álbumes.

Como siempre, intentaremos usar un constructor propio, de esta forma. Dentro de Domain/Entities, crea un archivo llamado Array+Album.swift

Necesitamos definir un constructor propio y modificar ligeramente Album. ¿Por qué? Pues porque una cosa es una entidad de Dominio y otra la forma en la que se representará en la pantalla. Llámalo Album+AlbumDTO.swift.

Así, nuestra función privada se quedará de la siguiente manera.

Si nos fijamos en el tipo de función. Se hace una request, se mapea a un tipo SearchDTO y luego se vuelve a mapear a [Album]. ¿Muchas cosas, no? Si, vale. Lo dejamos aquí. Cuando dividamos la aplicación en frameworks, veremos con más claridad, qué es lo que está pasando aquí.

Tenemos, también, que adaptar el ListTableViewCell.

Por último, nos queda implementar la transformación de inputs con outputs.

Ya lo tendríamos. Hemos separado los canales de entrada y los canales de salida. ¿Pero cómo se usaría en el UseCase? Vamos allá.

Desde el punto de vista del UseCase, tenemos que adaptar las entradas y las salidas al repository y hacerlo compatible con las futuras entradas que tendrá el ViewModel.

Ya solo nos queda, adaptar el ViewController.

Si ejecutamos la aplicación, obtendremos algo parecido a esto.

La aplicación la tendríamos pero queremos alguna cosa más, antes de terminar. Primero, habilitaremos un pequeño buscador.

El delegate UISearchController no está reactivado. Pero no es problema. Reactivar un delegado no es difícil, pero se usa una notación, que puede ser complicado. Aquí se encuentra una aproximación.

Crea dentro de Application/Extensions, el archivo UISearchController+Rx.swift. Y copia el contenido.

En el ViewController, añade el siguiente código dentro del viewDidLoad.

Hemos definido un objeto UISearchController y luego, enviamos el evento hacia el View Model.

Si ejecutamos, de nuevo, la app, nos daremos cuenta que a cada cambio del texto de búsqueda que realicemos, estaremos enviando un nuevo evento. Esto puede ser un problema.

En el ViewModel, podemos hacer lo siguiente.

  • debounce limita el número de eventos a un tiempo determinado
  • con un filter comprobando que la cadena tiene más de 3 carácteres estaremos evitando hacer llamadas con textos muy pequeños.

La paginación se ejecutará cuando la tabla haga scroll hasta el final. Necesitamos un evento que se lance cuando se llegue a un limite determinado.

Crea en Application/Extensions el siguiente fichero UIScroll+Rx.swift

Necesitamos un evento nuevo en los Inputs del ViewModel.

Actualizaremos el ViewController.

Una vez tenemos esto, repetiremos el paso en el Use Case y en el Repository.

Ahora, para la paginación usaremos el siguiente mecanismo. Pero lo modificaremos para adaptarlo a nuestro propósito.

https://gist.github.com/danielt1263/10bc5eb821c752ad45f281c6f4e3034b

Dentro de la carpeta Data, crea un fichero PaginationSink y copia la clase del enlace anterior.

PaginationUISource representa el elemento de la entrada. No necesitamos un evento de refresh. Necesitamos un observable search de tipo String.

PaginationSink tiene unas salidas determinadas.

  • isLoading
  • elements
  • error

Nota: merece la pena estudiar este algoritmo, aunque, en un inicio pueda parecer un poco pesado.

Con todo esto, el repository se quedará así.

Ya lo tendríamos.

Imaginemos ahora, que quisieramos añadir un texto en el título para saber si se está realizando una nueva request por paginación. Lo podríamos hacer así.

¿Qué hemos aprendido?

  • Moya, Codable con RxSwift y la capa de Data
  • Inputs y Outputs para tener flujos unidireccionales.
  • Una forma sencilla de paginación.

Seguiremos en la próxima entrega con frameworks. Cuando estabamos con ObjC era muy sencillo controlar las dependencias del namespace ya que teníamos que importar cualquier archivo. Con Swift, al desaparecer, es muy fácil cometer el error de poner dependencias de una capa en otra. Una forma sencilla es crear diferentes frameworks y veremos si tenemos alguna.

Saludos, Albert.

--

--