0% encontró este documento útil (0 votos)
234 vistas104 páginas

Ebook Android

Este documento presenta los primeros 7 pasos para desarrollar una aplicación Android para administrar productos, clientes y pedidos basados en una base de datos en un servidor, comenzando con la creación de una API REST para mostrar una lista de productos. Los pasos incluyen definir el propósito, recolectar requisitos, determinar usuarios, crear bocetos, definir fuentes de datos y sincronización, seleccionar herramientas y crear una versión inicial.

Cargado por

Paul Sumer
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
234 vistas104 páginas

Ebook Android

Este documento presenta los primeros 7 pasos para desarrollar una aplicación Android para administrar productos, clientes y pedidos basados en una base de datos en un servidor, comenzando con la creación de una API REST para mostrar una lista de productos. Los pasos incluyen definir el propósito, recolectar requisitos, determinar usuarios, crear bocetos, definir fuentes de datos y sincronización, seleccionar herramientas y crear una versión inicial.

Cargado por

Paul Sumer
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 104

Tutorial Para Desarrollar Una App

Android Para Productos, Clientes Y


Pedidos
(Nivel #1)

El ttulo es claro.

En esta serie de tutoriales te mostrar como crear una aplicacin que administre
productos, clientes y pedidos basados en una base de datos en un servidor.

Qu significa Nivel #1?

Qu el actual tutorial que ests leyendo tiene el siguiente alcance:

Vamos a ver crear una REST API para leer los productos que tienes en tu servidor
y ponerlos en una lista (RecyclerView).

Te mostrar los pasos, herramientas, y conceptos para desarrollar esta


caracterstica de una forma sencilla, limpia, mantenible y sobre todo entendible.

El cdigo completo de la app y el servicio REST puedes encontrarlo en la misma


carpeta de este tutorial.

App Productos
Al igual que en todos los tutoriales que he creado sobre Desarrollo Android, este
tambin trae una app de ejemplo llamada App Productos.

App Products es una aplicacin administrativa para una farmacia de gran tamao,
cuyo fin es movilizar su proceso de toma de pedidos y consulta de productos por
parte de los vendedores.

Nuestro objetivo es repasar su reconstruccin con todo nivel de detalle.

Qu te parece?

Puede que su contenido o contexto pueda diferir un poco a tus necesidades, ya que
no todos los problemas son iguales.
Pero he aqu la parte buena:

Te he creado una plantilla con la ruta ptima para que superes las adversidades
particulares de tu proyecto.

Quieres verla?

Bien!

Checklist Para Desarrollar Tu App De


Productos
Lo primero que vamos a escudriar, son los pasos precisos que daremos para
ejecutar exitosamente nuestra aplicacin.

Esto puedo variar de programador a programador (de equipo a equipo, si es tu


caso).

A m en particular me gusta seguir un orden predefinido de lo que voy a realizar.


Definiendo el orden, las condiciones de uso, las herramientas y las prcticas que
usar.

A la suma de estos factores se le llama Metodologa.

Independientemente de que profesin tengas o que proyecto vayas a realizar,


siempre tendrs una metodologa. Ya sea una copiada, improvisada, propia, o
hibrida.

Qu tal te va con la tuya?

Si no tienes. Te recomiendo que comiences con esta:

1. Definir el propsito general de la app

2. Recolectar requerimientos

3. Determinar quin usar la app

4. Crear wireframe de pantallas

5. Determinar fuentes de datos y sincronizacin

6. Seleccionar recursos y herramientas


7. Desarrollar versin cascaron de la app

8. Crear servicio REST

9. Implementar cliente HTTP con Retrofit

Quedas en libertad de mover, eliminar y agregar pasos que se ajusten a tu carcter


y conocimientos.

Eres t quien eliges tu ruta.

Yo solo te mostrar cmo ponerla en marcha en este instante

Paso #1. Definir El propsito De La App


El propsito es el objetivo general que deseas alcanzar con tu app.

Una simple frase de no ms de 3 renglones que resuma tu futuro perfecto.

Por ejemplo:

La App reducir los tiempos del anlisis de mitigacin de 4 horas a 1 hora

La App incrementar los tiempos de toma de pedidos de nuestros meseros en un


30%

Ves el formato?

Usamos una mtrica que evidencie la mejora de una situacin. Simple.

En el caso de App Productos tenemos:

Encontrar productos solicitados por un cliente en menos de 2 segundos y tomar


pedidos digitalmente

El Contexto de App Products


Esta aplicacin es solo una de las caractersticas que tendra una app que tome las
rdenes de un negocio.

La proyeccin futura es que sincronice los pedidos con un servidor remoto.

Sin embargo, por el momento, llamaremos a este tutorial como el Nivel 1.


Paso #2. Recolectar Necesidades
Esta etapa es muy conocida por la mayora de ingenieros de sistemas asociados al
anlisis de software.

Bsicamente aqu debes crear una lista con las funcionalidades que tendr el
sistema basado en los objetivos del usuario.

En las metodologas tradicionales a la necesidad se le califica como


Requerimiento.

En metodologas giles se le llama Historia de usuario.

(No entrar a explicar los alcances de ambos por cuestiones de simplicidad).

Ahora, aplicando este paso, supondremos que el usuario (administrador del


negocio, supervisor, t mismo, etc.) necesita:

Una lista de todos los productos del negocio

Un filtro de productos segn sus atributos

La bsqueda de productos por nombre

Ver detalle del producto

El login de sus vendedores

Aadir pedido

Modificar pedido

Eliminar pedido

Ver detalle de pedido

Administrar clientes. Detalle, insercin, modificacin y eliminacin


(OPCIONAL)

Imprimir factura de pedido

Sincronizar pedidos locales con el servidor


Necesidades de bases de datos: En este punto tambin puedes capturar las
entidades, relaciones y atributos que requieren persistencia.

Para ello puedes usar varios artefactos. Dos de los ms populares son: diagramas
entidad-relacin y un diccionario de datos.

El uso de ambos lo defino muy bien en mi ebook 8 Pasos para disear tus bases
de datos. En l vers estrategias para capturar dichas necesidades.

Ahora bien:

En el estado actual, App Products solo tiene la entidad Producto.

Qu propiedades posee?

Cdigo

Nombre

Descripcin

Marca

Precio

Unidades en stock

Imagen(es) de producto

Paso #3. Determinar Quin Usar La App


Al principio habamos dicho que la app es para los vendedores que toman los
pedidos en la farmacia.

Y esta es la clara respuesta: Vendedores de la farmacia.

Identifica el o los usuarios para los cuales desees crear la app.

Por ejemplo, podra que tambin debas desarrollar un mdulo para los
supervisores de los vendedores.

Suponiendo que ellos monitorearn la actividad de cada vendedor.


O tal vez un panel de administracin que muestre el resumen de las ventas y
productividad de los empleados.

Paso #4. Crear Bocetos


A continuacin vamos a crear mocks de la aplicacin segn nuestra experiencia
con los patrones de diseo en Android.

Qu herramientas puedes usar?

Papel + Lapiz + Cmara para capturas

Ninjamock

Pothoshop

Mockplus

balsamiq

Pidoco

Lucidchart

Proto.io

Cul de todas elegir?

En mi caso me gusta mucho ninjamock porque es gratuita y muy intuitiva de usar.


Adems permite una fcil comunicacin con clientes que tengan algn
conocimiento de desarrollo.

Pero si mi cliente no tiene ni idea, entonces uso proto.io para mostrarle


interacciones e interfaz de alto nivel.

Es solo mi punto de vista. Ya t irs probando que te va mejor.

Retomando

El estado actual de este tutorial solo tiene una pantalla: la lista de productos.
Y basada en ella, estos son los puntos de interaccin del usuario:

Tap en botn bsqueda > Abre pantalla de bsqueda


Tap en producto > Abre pantalla de detalle
Long tap en producto > Ripple Effect + Aparicin de Contextual Action Bar
(opcional)

Swipe to refresh > Refrescar


Endless scroll > Se cargan elementos adicionales a la lista
Tambin anotemos los estados adversos:

Estado sin productos > Mensaje de ausencia de productos

Estado de error > Toast con mensajes de error


Paso #5. Determinar Fuentes De Datos Y
Estrategias De Sincronizacin
Ojo aqu:

Debes definir muy bien qu pasar con la informacin de tus usuarios.

Unas buenas preguntas que te propongo a responder son:

En qu lugares se guardarn datos?

R/ Por el momento (nivel 1), en App Products tendremos una base de datos remota
(servidor) para persistencia. Tambin usaremos la memoria del dispositivo como
cach al mostrar elementos.

Habr sincronizacin de datos?... Si es as, que estrategias usars?

R/ S.

Las entidades se comportarn de esta forma:


Productos: predominan los cambios del servidor, ya que desde el cliente
Android no se modificarn.

Adems:

El usuario sincronizar manualmente la app con el gesto Swipe to refresh

Se enviarn modificaciones inmediatamente la persistencia local cambie


(futuro)

Se notificarn cambios del servidor con notificaciones push (futuro)

Si no hay conexin disponible para sincronizar, se programar una


actualizacin a penas se detecte el restablecimiento de la red (futuro).

Qu polticas usars para tomar datos?

R/

Se consultar al servidor remoto por defecto cuando no haya datos en la


base de datos local.

Luego de obtener datos de la nube, la fuente de datos local se actualiza con


dichos datos. Luego se pone en la cach y se muestra en la vista.

Paso #6. Elegir Recursos Y Herramientas


Cmo llevas tu diagnostico hasta aqu?

Al llegar a este punto, debes tener definido al menos un 70% de lo que har tu app.

Con todo y lo anterior Qu tal si definimos las herramientas, recursos y


tecnologas que necesitamos?

Comencemos por el lado del cliente:

Manejo de colecciones y precondiciones > Librera Guava

Carga eficiente y cach en disco de imgenes > Librera Glide

Base de datos local (futuro) > SQLiteOpenHelper + ContentProvider


Ejecutor de peticiones HTTP hacia la API: Retrofit

Ahora, el lado del servidor:

Lenguaje: PHP 5.6

Gestor de bases de datos: MySQL

Proveedor del servicio de hosting: Localhost (nuestro PC local).

En el nivel #2 consideraremos usar opciones como: Google Cloud Platform,


Amazon web services, Rackspace, DigitalOcean, Microsoft Azure, Heroku,

Formato de intercambio: JSON

Paso #7. Crear Cascarn


Por fin!

Nos ha llegado el momento de codificar.

En esta etapa crearemos una primera entrega a la que yo llamo el cascarn.

Por qu este nombre?

La razn es sencilla: validar lo ms pronto posible tu app.

Te olvidars por un momento de las fuentes de datos y te centrar en crear una app
que solo utilice informacin falsa.

Hars que tu usuario pruebe de forma temprana todas las interacciones y flujos, de
modo que obtengas el restante de opiniones que solo se dan en la accin real.

Esto nos lleva a los siguientes pasos:

1. Definir arquitectura y patrones

2. Determinar tareas de programacin

Todo claro hasta all?

Si es tu caso, entonces empecemos.


Arquitectura y patrones
La arquitectura que usaremos se llama Clean, tratada en este artculo de Robert
Martin.

Sus pros?

Permite separar conceptos

Aumenta la hermeticidad a la hora de usar tests

Reduce la dependencia de entes externos (bases de datos, APIs, sensores,


etc)

La UI se somete solo a mostrar datos (nada de God Activities)

Muchas, muchas, muchas ms

Clean architecture se divide en tres capas:

Presentacin: Aqu defines todo lo relacionado a la vista y las animaciones.


En nuestro caso aplicaremos Modelo-Vista-Presentador para cubrir dicha
capa. Sin embargo puedes usar MVC o MVVM.

Dominio: Aqu van las reglas del negocio definidas por casos de uso
(interactores) y las entidades bsicas (objetos planos java).

Datos: Esta capa contiene los datos de la cual se alimentarn la capa de


presentacin. Existen varios patrones que puedes usar para manejar los
datos. En particular, usaremos uno llamado Repositorio para generalizar la
toma desde mltiples fuentes de datos (SQLite, Realm, Memoria, Servidor,
archivos, etc.).

Para entenderlo de forma global, he creado esta ilustracin representativa:


Qu te parece?

Ya la conocas?

Generalmente se habla de usar MVP como arquitectura. Sin embargo este es solo
un patrn para simplificar la capa de presentacin.

Este artculo de Antonio Leiva te aclarar muy bien esta parte:

http://www.genbetadev.com/paradigmas-de-programacion/usando-mvp-e-
inversion-de-dependencias-para-abstraernos-del-framework-en-android

Cabe destacar que reducir la cantidad de elementos usados en esta arquitectura


con el fin de no abrumarte en esta primera construccin.

Lo importante es que elijas una forma de escribir tu cdigo que sea entendible para
ti y futuros lectores de tu proyecto.

Adems que puedas ejecutar tests hermticos.

Tareas de programacin
A continuacin, debes recopilar en orden las tareas que tienes que llevar a cabo de
forma incremental, hasta que hayas terminado tu app.

La prioridad con que elijas las tareas depende de tu contexto productivo.

Por ejemplo, tal vez solo requieras primero crear todas las UI de las actividades y
fragmentos.

O crear los componentes de las tres capas para una sola caracterstica (como
haremos aqu).

Dependiendo de que hayas decidido, como mnimo usa una lista para determinar
su posicin de realizacin.

Y si quieres ponle fechas de terminacin y duracin de ejecucin.


De la otra mano

veamos como planificaremos el orden de tareas en App Productos.

Tareas de programacin para App Products

1. Crear actividad de productos (clase Java + layout)

2. Crear fragmento de productos (clase Java + layout)

a. Crear entidad Producto

b. Crear adaptador de productos

c. Crear layout para tems de productos

3. Preparar fragmento

4. Crear contrato MVP

5. Implementar vista de productos

6. Implementar presentador de productos

7. Crear repositorio de productos

8. Crear fuentes de datos

9. Terminar presentador de productos

10. Establecer dependencias vista-presentador

11. Proveer Endless scroll

Fcil cierto?

Actuemos de una vez en estas tareas

Tarea #1. Crear actividad de productos


Abre Android Studio y crea un nuevo proyecto llamado App Productos.

Cmo debes configurarlo?


As:

Nombre del paquete: com.hermosprogramacion.premium.appproductos

SDK mnimo: 11

Actividad por defecto: Basic Activity

Nombre actividad: ProductsActivity

Paquetes Java
Antes que nada, organicemos la estructura de paquetes Java basados en la
arquitectura propuesta.

Veamos el propsito de cada uno:

Representa la capa de datos.

En el tendremos el manejador de la api,


data y el repositorio y fuentes de datos
(datasource/cloud, /memory)
asociados a los productos.

Gestiona los contenedores de


di
dependencias

Se refiere a la caracterstica de la lista


products de productos. Dentro encontrars los
elementos asociados a su dominio
(domain) y los componentes de
presentacin (MVP).

Lo siguiente es disear su interfaz.

Layout: Por defecto Android Studio te cre el layout activity_products.xml.

En su interior vers la AppBar junto a una etiqueta <include> que representa el


contenido principal.

Y un floating action button. Debido a que no lo necesitamos, elimnalo.

activity_products.xml

<?xml version="1.0" encoding="utf-8"?>


<android.support.design.widget.CoordinatorLayout
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"
android:fitsSystemWindows="true"
tools:context=".products.ProductsActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_products" />

</android.support.design.widget.CoordinatorLayout>

content_products.xml
Este archivo representa el contenido principal de la actividad.

Predefinidamente viene un RelativeLayout con un TextView en su interior.

No obstante, elimina el texto.


Como vamos a instalar un fragmento en la actividad, asigna al layout el id
products_container.

content_products.xml

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout 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:id="@+id/products_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".products.ProductsActivity"
tools:showIn="@layout/activity_products">
</RelativeLayout>

Lgica de la actividad
Esta parte va ya es conocida por ti.

Lo nico que har nuestra actividad ser agregar un fragmento.

Usa una transaccin tipo aadir del FragmentManager.

Recuerda que el lugar para hacerlo es onCreate().

ProductsActivity.java

public class ProductsActivity extends AppCompatActivity {

private Toolbar mToolbar;


private Fragment mProductsFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_products);

// Referencias UI
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mProductsFragment =
getSupportFragmentManager().findFragmentById(R.id.products_container);

// Setup
setUpToolbar();
setUpProductsFragment();

private void setUpToolbar() {


setSupportActionBar(mToolbar);
}

private void setUpProductsFragment() {


if (mProductsFragment == null) {
mProductsFragment = ProductsFragment.newInstance(null, null);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.products_container, mProductsFragment)
.commit();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_products, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}

Obviamente el fragmento an no est creado

pero eso se soluciona ejecutando la tarea #2.

Tarea #2. Crear fragmento de productos


A continuacin, da click derecho en el paquete products.

Luego selecciona New > Fragment > Fragment (Blank) y crea un fragmento
llamado ProductsFragment.
Desmarca Include interface callbacks, ya que por el momento no tendremos
interfaz entre el fragmento y la actividad.
fragment_products.xml
Lleg la hora de representar los bocetos que realizaste de la pantalla de productos.

Recuerda:

Tenemos una lista de productos como el view principal.

Pero tambin existen dos estados: carga y ausencia de tems.

Qu views usar?

Veamos

La lista de productos: Tenemos dos opciones. ListView y RecyclerView.

Puedes usar cualquiera, pero como la mayora de mis lectores me piden tutoriales
sobre el segundo, entonces ese ser nuestro objetivo.

Observa:

#1. Abre fragment_products.xml y establece como nodo raz un RelativeLayout.

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/products_content"
android:layout_width="match_parent"
android:layout_height="match_parent">

</RelativeLayout>

#2. Abre t archivo build.gradle, agrega la siguiente dependencia y sincroniza el


proyecto (Tools > Android > Sync Project with Gradle Files):

dependencies {

compile 'com.android.support:recyclerview-v7:24.2.0'
}

#3. Ahora agrega un RecyclerView.

Haz que se ajuste al padre y su linear manager sea del tipo LinearLayoutManager.
<android.support.v7.widget.RecyclerView
android:id="@+id/products_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F0F0F0"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"/>

Estado de carga: Ya sabemos que para refrescar los productos, usaremos el patrn
Swipe to refresh.

Y la solucin es la clase SwipeRefreshLayout.

La nica accin que debes realizar es envolver el RelativeLayout con la etiqueta


android.support.v4.widget.SwipeRefreshLayout y listo:

<android.support.v4.widget.SwipeRefreshLayout
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:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".products.ProductsFragment">

<RelativeLayout>

<android.support.v7.widget.RecyclerView>
...

Estado vaco: Cuando no tengamos productos en la aplicacin, mostraremos un


mensaje al usuario.

Cmo hacerlo?

Bueno, depende de tu imaginacin.

Pero un icono alusivo al vaco y un mensaje claro basta.

As que usemos un LinearLayout vertical, con un ImageView y un TextView.


<RelativeLayout
...>

<android.support.v7.widget.RecyclerView
... />

<LinearLayout
android:id="@+id/noProducts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">

<ImageView
android:id="@+id/noProductsIcon"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:tint="@android:color/darker_gray"
app:srcCompat="@drawable/ic_package_variant_closed" />

<TextView
android:id="@+id/noProductsText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/list_item_padding"
android:text="@string/no_products_message" />
</LinearLayout>
</RelativeLayout>

Si te fijas en la preview vers lo siguiente:

Para usar vectores en modo de compatibilidad usa app:srcCompat. El archivo


ic_package_variant_closed.xml lo encontrars en la carpeta res/drawable.
Crear entidad Producto
Ya sabemos que atributos usaremos para los productos (visita el paso #2 si no
recuerdas).

Ahora solo queda crear un objeto Java tradicional que represente la entidad del
negocio.

Aade una nueva clase llamada Product dentro de products/domain/model:


public class Product {

private String mCode;


private String mName;
private String mDescription;
private String mBrand;
private float mPrice;
private int mUnitsInStock;
private String mImageUrl;

public Product(float price, String name, String imageUrl) {


mCode = UUID.randomUUID().toString();
mPrice = price;
mName = name;
mImageUrl = imageUrl;
}

public String getCode() {


return mCode;
}

public String getName() {


return mName;
}

public String getDescription() {


return mDescription;
}

public String getBrand() {


return mBrand;
}

public float getPrice() {


return mPrice;
}

public String getImageUrl() {


return mImageUrl;
}

public Object getUnitsInStock() {


return mUnitsInStock;
}

public void setCode(String mCode) {


this.mCode = mCode;
}

public void setName(String mName) {


this.mName = mName;
}

public void setDescription(String mDescription) {


this.mDescription = mDescription;
}

public void setBrand(String mBrand) {


this.mBrand = mBrand;
}

public void setPrice(float mPrice) {


this.mPrice = mPrice;
}

public void setUnitsInStock(int mUnitsInStock) {


this.mUnitsInStock = mUnitsInStock;
}

public void setImageUrl(String mImageUrl) {


this.mImageUrl = mImageUrl;
}

public String getFormatedPrice() {


return String.format("$%s", mPrice);
}

public String getFormattedUnitsInStock() {


return String.format(Locale.getDefault(), "%d u.", mUnitsInStock);
}
}

Crear adaptador de productos


La lista no est completa si no tenemos un adaptador alimentndola.

Por eso, crea una nueva clase Java (File > New > Java Class) y llmala
ProductsAdapter.

Por si lo haz olvidado:

Extiende la clase de RecyclerView.Adapter<RecyclerView.ViewHolder>

Declara como variable miembro una lista de objetos Product

Aade un constructor que reciba una lista inicial de productos y una


referencia de escucha para clicks

Sobrescribe onCreateViewHolder(), onBindViewHolder() y getItemCount()

Crea una escucha para clicks. Si no tienes un buen nombre, llmala


OnProductClick
Crea una clase anidada que herede de RecyclerView.ViewHolder para
referenciar los views de los productos.

La has pillado?

Bien!

La implementacin debera quedarte as:


public class ProductsAdapter extends
RecyclerView.Adapter<RecyclerView.ViewHolder> {

private List<Product> mProducts;


private ProductItemListener mItemListener;

public ProductsAdapter(List<Product> products, ProductItemListener


itemListener) {
setList(products);
mItemListener = itemListener;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view;

view = inflater.inflate(R.layout.item_product, parent, false);


return new ProductsHolder(view, mItemListener);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int
position) {

if (viewHolder instanceof ProductsHolder) {


Product product = mProducts.get(position);
ProductsHolder productsHolder = (ProductsHolder) viewHolder;
productsHolder.price.setText(product.getFormatedPrice());
productsHolder.name.setText(product.getName());

productsHolder.unitsInStock.setText(product.getFormattedUnitsInStock());
Glide.with(viewHolder.itemView.getContext())
.load(product.getImageUrl())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.centerCrop()
.into(productsHolder.featuredImage);
}

public void replaceData(List<Product> notes) {


setList(notes);
notifyDataSetChanged();
}
private void setList(List<Product> notes) {
mProducts = checkNotNull(notes);
}

public void addData(List<Product> products) {


mProducts.addAll(products);
}

@Override
public int getItemCount() {
return getDataItemCount();
}

public Product getItem(int position) {


return mProducts.get(position);
}

public int getDataItemCount() {


return mProducts.size();
}

public class ProductsHolder extends RecyclerView.ViewHolder implements


View.OnClickListener {

public TextView name;


public TextView price;
public ImageView featuredImage;
public TextView unitsInStock;

private ProductItemListener mItemListener;

public ProductsHolder(View itemView, ProductItemListener listener) {


super(itemView);
mItemListener = listener;
name = (TextView) itemView.findViewById(R.id.product_name);
price = (TextView) itemView.findViewById(R.id.product_price);
unitsInStock = (TextView)
itemView.findViewById(R.id.units_in_stock);
featuredImage = (ImageView)
itemView.findViewById(R.id.product_featured_image);
itemView.setOnClickListener(this);
}

@Override
public void onClick(View v) {
int position = getAdapterPosition();
Product product = getItem(position);
mItemListener.onProductClick(product);

}
}

public interface ProductItemListener {


void onProductClick(Product clickedNote);

}
Aspectos a resaltar:

Usamos la librera Glide para cargar la imagen del producto desde la url. Recuerda
incluir esta dependencia para llevarla a cabo:

compile 'com.github.bumptech.glide:glide:3.7.0'

replaceData() renueva todos los datos del adaptador, pero addData() agrega sobre
los ya existentes.

Ves que en onBindViewHolder() uso el layout item_product.xml?

Obviamente, ya que te estar marcando error.

Mantente conmigo para ver cmo crear el diseo

Crear layout de tems


Prate en el directorio res/layout, haz click derecho y selecciona New > Layout
resource file.

Usa el nombra item_product.xml.

Ahora, la pregunta es:

Cmo diseo el producto en la lista?

Bueno el objetivo es ir a esto:


A mi parecer podemos usar un RelativeLayout.

Por qu?

Flexibilidad.

Las posiciones que tienen los elementos exigen orientaciones con respecto al
padre y a los hermanos.

Siendo as. Los views tendran estas caractersticas:

Nombre (TextView): Alineado a la derecha del padre y pegado a la imagen del


producto. No debe superar 1 lnea de texto.

Precio (TextView): Alneado en derecha + inferior.

Unidades en stock (TextView): Por debajo del nombre y pegado a la imagen.

Imagen del producto (ImageView): Alineado a la izquierda del padre, tamao


de 72dp x 72dp

VITAL:

Envolveremos dicho RelativeLayout con una CardView.

Te acuerdas de la dependencia que se debe agregar?

Mira:
compile 'com.android.support:cardview-v7:24.2.0'

Retornandoen cdigo el tem quedara as:


item_product.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="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:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="2dp"
android:foreground="?attr/selectableItemBackgroundBorderless"
card_view:cardPreventCornerOverlap="true"
card_view:cardUseCompatPadding="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="8dp">

<ImageView
android:id="@+id/product_featured_image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_alignParentLeft="true"
android:layout_marginRight="8dp" />

<TextView
android:id="@+id/product_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/product_featured_image"
android:maxLength="65"
android:maxLines="1"
android:text="New Text"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
tools:text="Piperacina Compuesta" />

<TextView
android:id="@+id/product_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?colorPrimary"
tools:text="$44" />

<TextView
android:id="@+id/units_in_stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/product_name"
android:layout_toRightOf="@+id/product_featured_image"
android:text="New Text"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="20 u." />

</RelativeLayout>
</android.support.v7.widget.CardView>

Efecto Ripple: Si quieres que el efecto Ripple funcione sobre el tem, usa la
caracterstica android:foreground del CardView para definirle el ripple por defecto
del framework ?attr/selectableItemBackgroundBorderless.

Tarea #3. Preparar fragmento


El siguiente paso es que relaciones programticamente los del layout.

Para hacerlo, haz esto:

Agrega variables miembros


Al inicio de ProductsFragment, agrega campo global llamado mProductsList para el
reciclador.

Tambin agrega una para el adaptador llamada mProductsAdapter.


private RecyclerView mProductsList;
private ProductsAdapter mProductsAdapter;

Algo ms?

Claro!

Recuerda la escucha de clicks. El SwipeRefreshLayout y el view de vaco.


private SwipeRefreshLayout mSwipeRefreshLayout;
private View mEmptyView;
private ProductItemListener mItemListener = new ProductItemListener() {
@Override
public void onProductClick(Product clickedProduct) {
// Aqu lanzaras la pantalla de detalle del producto
}
};

Crea instancia del adaptador


Ahora, en onCreate(), crea un nuevo adaptador.

Pasa como parmetro la escucha y una nueva lista.

Su capacidad inicial ser 0, ya que an no tenemos elementos.


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProductsAdapter = new ProductsAdapter(new ArrayList<Product>(0),
mItemListener);

Obtn referencias UI
Usa findViewById() y consigue todos los elementos de la interfaz:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_products, container, false);

// Referencias UI
mProductsList = (RecyclerView) root.findViewById(R.id.products_list);
mEmptyView = root.findViewById(R.id.noProducts);
mSwipeRefreshLayout = (SwipeRefreshLayout)
root.findViewById(R.id.refresh_layout);

// Setup
setUpProductsList();
setUptRefreshLayout();

return root;
}

Relaciona la lista con el adaptador


Crea un mtodo llamado setUpProductsList() para vincular la lista con el adaptador.
private void setUpProductsList() {
mProductsList.setAdapter(mProductsAdapter);
mProductsList.setHasFixedSize(true);

}
Este mtodo queda abierto para ms configuraciones relacionadas a la lista.

Prepara el swipe to refresh


Si te parece, cambia el esquema de colores del indicador en SwipeRefreshLayout a
travs del mtodo SetColorSchemeColors().

Y aade una escucha para disparar acciones al detectar el gesto.


private void setUptRefreshLayout() {
mSwipeRefreshLayout.setColorSchemeColors(
ContextCompat.getColor(getActivity(), R.color.colorPrimary),
ContextCompat.getColor(getActivity(), R.color.colorAccent),
ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));

mSwipeRefreshLayout.setOnRefreshListener(new
SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {

}
});
}

Tarea #4. Crear contrato MVP


Por lo general para representar el patrn MVP, se requiere una interfaz por cada
elemento: modelo, vista y presentador.

Esto permite vincular la triada de forma desconectada a los componentes


particulares.

Y queda mucho mejor si podemos tener sus definiciones en una interfaz de


contrato.

Entonces.

Crea una interfaz nueva yendo a New > Java Class : Interface. Denomnala como
ProductsMvp.
Como te vena diciendo, incluimos dos interfaces para la vista y el presentador. El
modelo lo supeditamos a la capa de datos.
public interface ProductsMvp {
interface View {

interface Presenter {

}
}

Ahora,

Determina las acciones de la vista


Pregntate:

Qu debe mostrar la vista?

Si tomas el camino feliz, dirs una lista de productos.

Y es correcto!

La vista debe tener un mtodo para ello.


Incluymoslo en la interfaz View con la firma showProducts(List<Product>) (mostrar
productos).
void showProducts(List<Product> products);

El estado de carga.
En este caso debes crear un mtodo que inicie/termine la carga en la vista.

Una buena firma sera showLoadingState(boolean) (mostrar estado de carga?).


void showLoadingState(boolean show);

Y qu hay de los estados adversos?

Hacemos exactamente igual.

Por ejemplo

Para mostrar el estado de vaco, podemos usar la firma showEmptyState().


void showEmptyState();

Y para mostrar un error: showProductsError(String).


void showProductsError(String msg);

El pginado con Endless Scroll

La vista de un pginado se trata de agregar ms elementos a la lista cada vez que


se llegue al lmite de elementos.

Por esta razn necesitamos un mtodo para poner los productos de la pgina:
void showProductsPage(List<Product> products);

Por otro lado, como debemos mostrar un indicador circular de progreso al final de
la lista, entonces aadimos ese comportamiento:
void showLoadMoreIndicator(boolean show);
Al mismo tiempo, debemos evitar que se genere el indicador si ya no hay ms
datos. Por lo que un mtodo que nos ayude a encender/apagar dicha condicin
vendra bien:
void allowMoreData(boolean show);

Determina las acciones del presentador


Este quehacer es fcil.

El presentador solo debe recuperar los datos del repositorio y los pone en la vista.

As que cargar productos sera una excelente firma para su mtodo.


interface Presenter {
void loadProducts(boolean reload);
}

Por ahora este es su nica accin.

Pero cabe resaltar que la carga puede ser para refrescar el contenido. As que usa
el parmetro booleano reload para especificarlo.

El presentador tambin debe afectar el modelo para guardar, modificar y eliminar


datos. Pero en este tutorial no lo requerimos an.

Tarea #5. Implementar Vista de productos


Quin es la implementacin concreta de la vista?

El fragmento de productos.

El ser el que manipule los views existentes para darle vida al flujo de interfaz.

Teniendo en cuenta esto, prosigamos de la siguiente manera.

Implementa la vista en el fragmento


Bsico!

Usa implements sobre ProductsFragment para ProductsMvp.View:


public class ProductsFragment extends Fragment implements ProductsMvp.View {

Esto te obliga a sobrescribir los mtodos que creamos anteriormente:


@Override
public void showProducts(List<Product> products) {

@Override
public void showLoadingState(final boolean show) {

@Override
public void showEmptyState() {

@Override
public void showProductsError(String msg) {

@Override
public void showProductsPage(List<Product> products) {

@Override
public void showLoadMoreIndicator(boolean show) {

@Override
public void allowMoreData(boolean allow) {

Sobrescribe ShowProducts()
Lo primero es reemplazar los datos del adaptador (replaceData()) y luego mostrar
la lista (setVisibility(View.VISIBLE)) y ocultar el estado de vaco
(setVisibility(View.GONE).
@Override
public void showProducts(List<Product> products) {

mProductsAdapter.replaceData(products);

mProductsList.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
}

Sobrescribe showLoadingState()
Ahora iniciaremos la animacin del SwipeRefreshLayout.

Primero comprueba que el fragmento est disponible con getView().


Y luego usa el mtodo setRefreshing() con el parmetro booleano show de
showLoadingState().

Para asegurarte que no se sobreponen las llamadas, pon en cola el mensaje con
post().

@Override
public void showLoadingState(final boolean show) {
if (getView() == null) {
return;
}

mSwipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(show);
}
});
}

Sobrescribe showEmptyState()
Este mtodo es intuitivo.

Solo haz aparecer el view vaco y oculta la lista.


@Override
public void showEmptyState() {
mProductsList.setVisibility(View.GONE);
mEmptyView.setVisibility(View.VISIBLE);
}

Sobrescribe showProductsError()
Cuando vengan los errores desde el presentador es buena opcin mostrarlos con
un Toast.

El cdigo es fcil:
@Override
public void showProductsError(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_LONG)
.show();
}

Sobrescribe showProductsPage()
Apila los productos de la pgina en el adaptador con addData().
@Override
public void showProductsPage(List<Product> products) {
mProductsAdapter.addData(products);
}

showLoadMoreIndicator() y allowMoreData() los veremos ms adelante.

Tarea #6. Implementar el Presentador de productos


Llegando hasta este punto ya tenemos la UI resplandeciente.

Pero claro

faltan los datos

Cmo obtenerlos?

De la capa de presentacin debemos usar una interfaz que nos permita solicitar la
informacin a la capa de dominio.

Dicha interfaz puede ser un Interactor (caso de uso). Un elemento que sigue el
patrn Command.

Sin embargo, no crearemos este elemento por el momento.

No quiero que realizar este tutorial sea duro para ti.

Entonces, Qu haremos?

Comunicaremos directamente el presentador con el repositorio de productos de la


siguiente manera

Crear presentador
Aade una nueva clase al paquete products llamada ProductsPresenter.

Esta ser la implementacin concreta del componente.

Hazlo comportar como


Implementa sobre l la interfaz ProductsMvp.Presenter y escribe su mtodo
loadProducts().

public class ProductsPresenter implements ProductsMvp.Presenter {

@Override
public void loadProducts(final boolean reload) {

}
}

Genera un constructor
Lo primero que pondrs ser un constructor.

Qu parmetros debe recibir?

Recuerda la triada. El presentador lleva y trae entre la vista y el modelo.

As que ambos son requeridos para el funcionamiento natural de este.


private final ProductsRepository mProductsRepository;
private final ProductsMvp.View mProductsView;

public ProductsPresenter(ProductsRepository productsRepository,


ProductsMvp.View productsView) {
mProductsRepository = checkNotNull(productsRepository);
mProductsView = checkNotNull(productsView);
}

(En el otro paso crearemos el componente ProductsRepository. Por ahora djalo


expresado)

Sobrescribe loadProducts()
Cmo funciona la carga de productos?

Dejame resumirlo en este diagrama de flujo:


El concepto de primera carga se relaciona con el inicio de la aplicacin.

Al iniciar, no habr datos. Por lo que tendremos una bandera booleana que lleve
este seguimiento.

Sumado a eso, si el usuario exigi recargar los datos, entonces se muestra el


indicador de carga, se le avisa al repositorio que recargaremos los datos y la
pgina actual (un entero) ser reseteada a 1.

Si dichas condiciones no se cumplen, entonces mostramos el indicador de cargar


ms y aumentamos en uno la pgina a mostrar.

Terminando cualquiera de los caminos, se cargan los productos del repositorio.


En cdigo esta lgica sera as:
@Override
public void loadProducts(final boolean reload) {
final boolean reallyReload = reload || isFirstLoad;

if (reallyReload) {
mProductsView.showLoadingState(true);
mProductsRepository.refreshProducts();
mCurrentPage = 1;
} else {
mProductsView.showLoadMoreIndicator(true);
mCurrentPage++;
}

mProductsRepository.getProducts(..);

Tarea #7. Crear repositorio de productos


La comunicacin directa desde la capa de datos hacia el presentador requiere una
interfaz asociada al repositorio.

Qu comportamientos debe tener?

Simplemente obtener productos

De la otra mano tenemos que esta operacin tiene probabilidad de ejecutarse


indefinidamente (latencia de red, lectura en disco, cantidad de elementos, etc.).

As que para saber como resulta (xito, fallo o proceso), es buena idea crear
interfaces para capturar estos eventos.

Todo claro hasta all?

Veamos el cdigo

Crear interfaz del repositorio


Aade una nueva interfaz al paquete data/products llamada IProductsRepository.

Pon un mtodo para la obtencin de productos:


public interface IProductsRepository {
interface GetProductsCallback {

void onProductsLoaded(List<Product> products);

void onDataNotAvailable(String error);


}
void getProducts(GetProductsCallback callback);

void refreshProducts();
}

GetProductsCallback procesa el flujo exitoso (onProductsLoaded()) y fallido


(onProductsLoadError()) de la carga de productos.

Crear implementacin del repositorio


Qu hacemos en esta parte?

Ya puedes intuir que crears una clase llamada ProductsRepository e


implementars la interfaz anterior.
public class ProductsRepository implements IProductsRepository {

Lo siguiente es poner como elementos miembros las fuentes de datos de memoria


y nube (en breves minutos las crearemos).

Tambin el contexto donde lo usaremos y una bandera para saber si hay que
refrescar los datos.
private final IMemoryProductsDataSource mMemoryProductsDataSource;
private final ICloudProductsDataSource mCloudProductsDataSource;
private final Context mContext;

private boolean mReload;

Ahora, crea un constructor que reciba instancias de las fuentes de datos.

(Usa el mtodo Preconditions.checkNotNull() de Guava para comprobar su


contenido)
public ProductsRepository(IMemoryProductsDataSource memoryDataSource,
ICloudProductsDataSource cloudDataSource,
Context context) {
mMemoryProductsDataSource = checkNotNull(memoryDataSource);
mCloudProductsDataSource = checkNotNull(cloudDataSource);
mContext = checkNotNull(context);
}

Polticas para fuentes de datos


Define claramente las polticas que usars para los productos.

Retoma tus estrategias de sincronizacin de esta entidad.


Cules son las reglas para tomar una fuente de datos u otra?

Para este ejemplo dijimos que los productos tienen un rol esclavo, es decir, en la
app solo sern consultados.

El usuario no podr crear, modificar o eliminar productos.

Eso nos lleva a establecer que...

Si hay datos en memoria y no se orden una recarga, se mostrarn directamente


sin consultar al servidor. Si es una recarga, entonces pedir datos al servidor

Implementar polticas
Hay varias formas de hacer esto.

La que usaremos se basa en incluir la lgica en cada mtodo del repositorio.

Pero por otro lado Fernando Cejas nos muestra cmo usar un patrn Factory para
elegir la fuente de datos aisladamente:

Ejemplo Factory para fuentes de datos

Basado en lo que dijimos ahora el algoritmo se traduce a:


@Override
public void getProducts(final GetProductsCallback callback) {

if (!mMemoryProductsDataSource.mapIsNull() && !mReload) {


getProductsFromMemory(callback);
return;
}

if (mReload) {
getProductsFromServer(callback, criteria);
} else {
List<Product> products = mMemoryProductsDataSource.find();
if (products.size() > 0) {
callback.onProductsLoaded(products);
} else {
getProductsFromServer(callback);
}
}

Donde los mtodos getProductsFromMemory() y getProductsFromServer() procesan la


carga de productos para cada fuente:
Tarea #8. Crear Fuentes De Datos

Crear fuente de datos en memoria


Lo primero es generar una interfaz hacia el repositorio llamada
IMemoryProductsDataSource en data/products/datasource/memory.

Qu operaciones tendremos?

Obtencin (find())

Guardado (save())

Eliminacin de todos los registros (deleteAll())

Comprobacin de existencia de datos (mapIsNull())

Interpretando lo anterior, escribe el siguiente cdigo:


public interface IMemoryProductsDataSource {
List<Product> find(ProductCriteria criteria);

void save(Product product);

void deleteAll();

boolean mapIsNull();
}

Ahora en el mismo paquete, crea una nueva clase llamada


MemoryProductsDataSource e implementa la interfaz anterior.

public class MemoryProductsDataSource implements IMemoryProductsDataSource {

Declara como miembro un HashMap<String, Product> para almacenar


temporalmente los productos.
private static HashMap<String, Product> mCachedProducts;

Sus operaciones son sencillas. Usa los mtodos put(), remove(), clear() y values()
para satisfacer los mtodos propuestos:
@Override
public List<Product> find() {

ArrayList<Product> products =
Lists.newArrayList(mCachedProducts.values());
return products;
}

@Override
public void save(Product product) {
if (mCachedProducts == null) {
mCachedProducts = new LinkedHashMap<>();
}
mCachedProducts.put(product.getCode(), product);
}

@Override
public void deleteAll() {
if (mCachedProducts == null) {
mCachedProducts = new LinkedHashMap<>();
}
mCachedProducts.clear();
}

@Override
public boolean mapIsNull() {
return mCachedProducts == null;
}

Crear fuente de datos en la nube (falsa)


Digo falsa porque an no tendr datos reales. La crearemos luego de haber
desarrollado nuestro servicio web (REST API).

Continuando:

Al igual que hicimos para la fuente en memoria, crea una interfaz llamada
ICloudProductsDataSource dentro de data/products/datasource/cloud.

En esta las acciones son ms limitadas.

Solo obtendremos productos (getProducts()) y proporcionaremos callbacks para


saber el resultado de la peticin (ProductServiceCallback).

public interface ICloudProductsDataSource {

interface ProductServiceCallback {

void onLoaded(List<Product> products);

void onError(String error);

void getProducts(ProductServiceCallback callback);

}
Lo siguiente es crear una clase llamada CloudProductsDataSource con la interfaz de
fuentes.

public class CloudProductsDataSource implements ICloudProductsDataSource {

Ahora pon como miembro un HashMap<String, Product> como hicimos en la fuente


de memoria.

(Agrega algunos datos de prueba para verlos en la interfaz)

private static HashMap<String, Product> API_DATA;

static {
API_DATA = new LinkedHashMap<>();
for (int i = 0; i < 100; i++) {
addProduct(43, "Producto " + (i + 1),
"file:///android_asset/mock-product.png");
}
}

private static void addProduct(float price, String name, String imageUrl) {


Product newProduct = new Product(price, name, imageUrl);
API_DATA.put(newProduct.getCode(), newProduct);
}

La imagen mock-product.png se encuentra en la carpeta main/assets. El esquema


file:///android_asset se usa para referirnos a ese lugar.

El siguiente paso es implementar GetProducts().

Sera un poco ms realista si simulamos con un Handler el tiempo de latencia para


la peticin.

As veremos la animacin consistente del SwipeRefreshLayout.


@Override
public void getProducts(final ProductServiceCallback callback) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
callback.onLoaded(Lists.newArrayList(API_DATA.values()));
}
}, LATENCY);
}
Donde LATENCY es una constante para retrasar la simulacin 2 segundos:
private static final long LATENCY = 2000;

Completemos el repositorio
Ahora s!

Usemos las fuentes de datos en el repositorio teniendo en cuenta la poltica


definida:
private void getProductsFromMemory(GetProductsCallback callback) {

callback.onProductsLoaded(mMemoryProductsDataSource.find());
}

private void getProductsFromServer(final GetProductsCallback callback) {

if (!isOnline()) {
callback.onDataNotAvailable("No hay conexin de red.");
return;
}

mCloudProductsDataSource.getProducts(
new ICloudProductsDataSource.ProductServiceCallback() {
@Override
public void onLoaded(List<Product> products) {
refreshMemoryDataSource(products);
getProductsFromMemory(callback);
}

@Override
public void onError(String error) {
callback.onDataNotAvailable(error);
}
});
}

private boolean isOnline() {


ConnectivityManager cm = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnectedOrConnecting();
}

private void refreshMemoryDataSource(List<Product> products) {


mMemoryProductsDataSource.deleteAll();
for (Product product : products) {
mMemoryProductsDataSource.save(product);
}
mReload = false;
}

@Override
public void refreshProducts() {
mReload = true;
}
Fijate que cuando se termina la carga de productos desde el servidor, la fuente de
datos en memoria se refresca con resfreshMemoryDataSource().

Solo se trata de la eliminacin completa del contenido actual y el guardado


posterior de los elementos.

Recuerda comprobar si dispones de conexin antes de ejecutar la toma de


productos desde la nube (isOnline()).

Tarea #9. Terminar presentador de productos


Vuelve al presentador de productos para implementar loadProducts().

La idea es llamar a GetProducts ().

Qu debes hacer en los casos de retorno?

Echale un ojo al diagrama de flujo (haz zoom para verlo en detalle):


INICIO

TRUE
Carga exitosa? FALSE

Ocultar indicador de carga

Ocultar indicador de "cargar ms"

TRUE FALSE
Hay productos?

Mostrar error

FALSE FALSE
TRUE TRUE
Refresco? Refresco?

Ocultar indicador "cargar ms"

Ocular indicador
Mostrar estado vaco
"cargar ms"
Mostrar productos Mostrar pgina de productos

Permitir "cargar ms"


No permitir
"cargar ms"

Primer carga = FALSE

FIN
Copiado?

Excelente!

Observa:
mProductsRepository.getProducts(
new ProductsRepository.GetProductsCallback() {
@Override
public void onProductsLoaded(List<Product> products) {
mProductsView.showLoadingState(false);
processProducts(products, reallyReload);

// Ahora si, ya no es el primer refresco


isFirstLoad = false;
}

@Override
public void onDataNotAvailable(String error) {
mProductsView.showLoadingState(false);
mProductsView.showLoadMoreIndicator(false);
mProductsView.showProductsError(error);
}
});

Donde processProducts() bifurca los resultados as:


private void processProducts(List<Product> products, boolean reload) {
if (products.isEmpty()) {
if (reload) {
mProductsView.showEmptyState();
} else {
mProductsView.showLoadMoreIndicator(false);
}
mProductsView.allowMoreData(false);
} else {

if (reload) {
mProductsView.showProducts(products);
} else {
mProductsView.showLoadMoreIndicator(false);
mProductsView.showProductsPage(products);
}

mProductsView.allowMoreData(true);
}
}

Tarea #10. Establecer dependencias entre Vista-


Presentador
A todas estas Dnde se crea el presentador? Cul es su contexto?

Dado que se relaciona con la vista, el fragmento es un buen lugar para mantenerlo.
En consecuencia, ve al fragmento y crea un miembro presentador.
private ProductsPresenter mProductsPresenter;

Luego en onActivityCreated(), inicia su instancia.


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mProductsAdapter = new ProductsAdapter(new ArrayList<Product>(0),
mItemListener);
mProductsPresenter = new ProductsPresenter(
DependencyProvider.provideProductsRepository(getActivity()),
this);

setRetainInstance(true);
}

Cul es la razn para obtener el repositorio desde el proveedor externo


DependencyProvider?

La respuesta concreta:

Para eliminar la responsabilidad del presentador de saber cmo crear el


repositorio.

De esta forma inyectamos la dependencia ensamblando los componentes.

As que dentro del paquete di agrega una clase con ese nombre aade los
siguientes mtodos:
public final class DependencyProvider {

private static Context mContext;


private static MemoryProductsDataSource memorySource = null;
private static CloudProductsDataSource cloudSource = null;
private static ProductsRepository mProductsRepository = null;

private DependencyProvider() {
}

public static ProductsRepository provideProductsRepository(@NonNull Context


context) {
mContext = checkNotNull(context);
if (mProductsRepository == null) {
mProductsRepository = new ProductsRepository(getMemorySource(),
getCloudSource(), context);
}
return mProductsRepository;
}

public static MemoryProductsDataSource getMemorySource() {


if (memorySource == null) {
memorySource = new MemoryProductsDataSource();
}
return memorySource;
}

public static CloudProductsDataSource getCloudSource() {


if (cloudSource == null) {
cloudSource = new CloudProductsDataSource();
}
return cloudSource;
}
}

Ahora, en onViewCreated() ejecuta el mtodo loadProducts() para cargar la lista si el


fragmento no ha sido retenido an:
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(savedInstanceState==null) {
mProductsPresenter.loadProducts(false);
}
}
Tarea #11. Proveer Scroll Infinito Al RecyclerView

El scroll infinito o endless scroll es la carga de ms datos al llegar al lmite de tems


visibles.

He aqu como implementarlo:

Procesar eventos de scroll


#1. Dentro de products crea una nueva clase llamada InfiniteScrollListener,
extindela de RecyclerView.OnScrollListener y sobrescribe su mtodo onScrolled().
public abstract class InfinityScrollListener extends
RecyclerView.OnScrollListener {

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}

Si eres intuitivo, deducirs que OnScrollListener nos reporta eventos de scroll a


travs de onScrolled().

#2. Para que la escucha detecte cuando se est cargando datos o cuando se
terminaron los datos, crea una interfaz llamada DataLoading dentro de products.

Incluye dos controladores para los eventos mencionados.

interface DataLoading {
boolean isLoadingData();

boolean isThereMoreData();
}

#3. Enseguida, aade los siguientes miembros:

private static final int VISIBLE_THRESHOLD = 5;


private final LinearLayoutManager mLayoutManager;
private final DataLoading mDataLoading;

El primero es la cantidad requerida de tems visibles antes del final, para comenzar
a cargar una nueva pgina.

mLayoutManager nos ser de utilidad al obtener informacin sobre los tems actuales.

Y mDataLoading es el punto de entrada para condicionar las acciones.

#4.Toma en el constructor una instancia del LinearLayoutManager y del DataLoading.


public InfinityScrollListener(DataLoading dataLoading, LinearLayoutManager
linearLayoutManager) {
mDataLoading = checkNotNull(dataLoading);
mLayoutManager = checkNotNull(linearLayoutManager);
}

#5. Cmo avisar que es momento de una carga nueva?

Incluye un mtodo abstracto llamado onLoadMore().

public abstract void onLoadMore();

#6. Ahora, es turno de la lgica.

Para determinar las condiciones donde se inicia otra carga, usaremos estas
variables:

visibleItemCount: La cantidad de tems visibles actualmente en la lista.

totalItemCount: Retorna la cantidad total de tems en el adaptador.

firstVisibleItem: Posicin de primer tem visible en la lista.

Con estos datos, establece esta condicin:

Si la cantidad total de tems menos la cantidad visibles, es menor o igual que la


suma del primer tem visto ms el threshold, entonces

Si sucede, actualiza la pgina, dispara el evento onLoadMore()


@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy < 0 || mDataLoading.isLoadingData() ||
!mDataLoading.isThereMoreData()) return;

final int visibleItemCount = recyclerView.getChildCount();


final int totalItemCount = mLayoutManager.getItemCount();
final int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();

if ((totalItemCount - visibleItemCount) <= (firstVisibleItem +


VISIBLE_THRESHOLD)) {
onLoadMore();
}
}
Flujos adversos: evita la comprobacin si el diferencial dy es menor a 0, si se estn
cargando datos o si ya no hay ms datos que cargar.

Permite al adaptador de productos reportar estados


Recuerdas la interfaz DataLoading?

Bien.

Implemntala sobre ProductsAdapter.

public class ProductsAdapter extends


RecyclerView.Adapter<RecyclerView.ViewHolder>
implements DataLoading

y sobrescribe los mtodos.

@Override
public boolean isLoadingData() {
return false;
}

@Override
public boolean isThereMoreData() {
return false;
}

Crea dos banderas miembro para hacerle seguimiento a ambos estados:


@Override
public boolean isLoadingData() {
return mLoading;
}

@Override
public boolean isThereMoreData() {
return mMoreData;
}

Asigna escucha a la lista


Dentro del fragmento de productos, ve al setup del recycler view y agrega una
nueva escucha con addOnScrollListener().

Sobrescibe onLoadMore() para cargar productos del presentar:


private void setUpProductsList() {
mProductsList.setAdapter(mProductsAdapter);

final LinearLayoutManager layoutManager =


(LinearLayoutManager) mProductsList.getLayoutManager();

// Se agrega escucha de scroll infinito.


mProductsList.addOnScrollListener(
new InfinityScrollListener(mProductsAdapter, layoutManager) {
@Override
public void onLoadMore() {
mProductsPresenter.loadProducts(false);
}
});
}

Crea un criterio de seleccin de pginas


Si queremos que solo se carguen los elementos pertenecientes a una pgina de n
elementos, se necesario crear un filtro para ello.

Cmo haces eso?

#1. Dentro de products crea un paquete llamado domain. Dentro de este crea otro
llamado criteria.

#2. En segundo lugar, aade una interfaz llamada ProductCriteria.

Su funcin es comprobar si los elementos de una lista cumplen con ciertos


criterios.

public interface ProductCriteria {


List<Product> match(List<Product> products);
}

#3. Ahora crea una implementacin llamada PagingProductCriteria.

Recibe en su constructor dos parmetros: la pgina a cargar y el lmite de


elementos por pgina.
public PagingProductCriteria(int page, int limit) {
mPage = page;
mLimit = limit;
}

#4. Ahora sobrescribe match() y obtn los {mLimit} tems que pertenezcan a la
pgina {mPage}.
public class PagingProductCriteria implements ProductCriteria {

private final int mPage;


private final int mLimit;

public PagingProductCriteria(int page, int limit) {


mPage = page;
mLimit = limit;
}

@Override
public List<Product> match(List<Product> products) {
List<Product> criteriaProducts = new ArrayList<>();

// Sanidad
if(mLimit <= 0 || mPage <=0){
return criteriaProducts;
}

int size = products.size();


int numPages = size / mLimit;
int a,b;

if (mPage > numPages) {


return criteriaProducts;
}

a = (mPage - 1) * mLimit;

if (a == size) {
return criteriaProducts;
}

b = a + mLimit;

criteriaProducts = products.subList(a, b);

return criteriaProducts;

}
}

Pasar criterio al repositorio


Bien hecho!

Lo ltimo es pasar el criterio en el mtodo getProducts() del repositorio y en los


mtodos de devolucin en las fuentes de datos.

Fjate:

En ProductsPresenter pasamos un nuevo criterio de paginado cuando llamamos al


repositorio en loadProducts():
// Ahora, preparamos el criterio de paginacin
ProductCriteria criteria = new PagingProductCriteria(page, PRODUCTS_LIMIT);

mProductsRepository.getProducts(
new ProductsRepository.GetProductsCallback() {
@Override
public void onProductsLoaded(List<Product> products) {
mProductsView.showLoadingState(false);
processProducts(products, reload);
}

@Override
public void onDataNotAvailable() {
mProductsView.showLoadingState(false);
mProductsView.showProductsError("");
}
},
criteria);

En CloudProductsDatasource, no usaremos el criterio ya que siempre consultaremos


los productos de la API. As que pasamos null en su mtodo getProducts().

Sin embargo, MemoryProductsDatasource si debe tratar el criterio.

En find() debes llamar a match() para retornar solo los elementos necesarios:

@Override
public List<Product> find(ProductCriteria criteria) {
ArrayList<Product> products =
Lists.newArrayList(mCachedProducts.values());
return criteria.match(products);
}

Crear Un View Para Carga En El Adaptador


En la carga de tems adicionales en nuestra lista, por lo general se usa una
ProgressBar circular para mostrarle al usuario que ya viene en camino.

Este elemento debe posicionarse en la ltima casilla del recycler.

Se te ocurre como hacerlo?

Si estabas pensando en el mtodo getItemViewType(), vas bien.

Concretemos la idea:

#1. Agrega dos constantes de tipo. Una para los productos y otra para el tem de
carga:
private final static int PRODUCT_ITEM_TYPE = 1;
private final static int FOOTER_ITEM_TYPE = 2;

#2. Ahora sobrescribe getItemViewType().

En esta instancia la interrogante es:

Qu condicin uso para retornar un tipo u otro?

Mira lo que yo pienso:

La nica forma de que deba aparecer un elemento de carga, es que la posicin


actual exceda a la cantidad total de tems

adems, la cantidad total debe ser mayor a 0.

Traducido a cdigo esto sera:


@Override
public int getItemViewType(int position) {
if (position < getDataItemCount() && getDataItemCount() > 0) {
return TYPE_PRODUCT;
}

return TYPE_LOADING_MORE;
}

#3. Despus, ve a onCreateViewHolder() e infla el view de carga si el parmetro


viewType coincide con TYPE_LOADING_MORE.

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view;

if (viewType == TYPE_LOADING_MORE) {
view = inflater.inflate(R.layout.item_loading_footer, parent, false);
return new LoadingMoreHolder(view);
}

view = inflater.inflate(R.layout.item_product, parent, false);


return new ProductsHolder(view, mItemListener);
}

El layout item_loading_footer.xml es bsico. Tendremos una barra de progreso


dentro de un LinearLayout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:minHeight="?listPreferredItemHeight"
android:gravity="center"
android:layout_height="wrap_content">

<ProgressBar
android:layout_width="24dp"
android:layout_height="24dp"
android:id="@+id/progressBar"
android:indeterminate="true" />
</LinearLayout>

Y el holder contendr solo la recoleccin de dicho view:


private class LoadingMoreHolder extends RecyclerView.ViewHolder {
public ProgressBar progress;

public LoadingMoreHolder(View view) {


super(view);
progress = (ProgressBar) view.findViewById(R.id.progressBar);
}
}

#4. Luego en el onBindViewHolder() procesa el tipo de view llamado a


getItemViewType().

Si es de tipo carga, entonces genera un nuevo mtodo llamado


bindLoadingViewHolder(), donde se muestre la barra de progreso si la carga est
activa y si hay ms datos.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
switch (getItemViewType(position)) {
case TYPE_PRODUCT:
Product product = mProducts.get(position);
ProductsHolder productsHolder = (ProductsHolder) viewHolder;
productsHolder.price.setText(product.getFormatedPrice());
productsHolder.name.setText(product.getName());
Glide.with(viewHolder.itemView.getContext())
.load(product.getImageUrl())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.centerCrop()
.into(productsHolder.featuredImage);
break;
case TYPE_LOADING_MORE:
bindLoadingViewHolder((LoadingMoreHolder) viewHolder, position);
break;
}

private void bindLoadingViewHolder(LoadingMoreHolder viewHolder, int position) {


viewHolder.progress.setVisibility((position > 0 && mLoading && mMoreData)
? View.VISIBLE : View.INVISIBLE);
}
Aadir/Remover el tem de carga
En esta parte haremos que el adaptador obedezca al presentador de productos.

Cmo?

Usaremos dos mtodos para mostrar/ocultar el tem de carga.

Y otro para avisar si hay ms datos o si ya se terminaron.

public void dataStartedLoading() {


if (mLoading) return;
mLoading = true;
notifyItemInserted(getLoadingMoreItemPosition());
}

public void dataFinishedLoading() {


if (!mLoading) return;
mLoading = false;
notifyItemRemoved(getLoadingMoreItemPosition());
}

public void setMoreData(boolean more) {


mMoreData = more;
}

dataStartedLoading() levanta la bandera de carga activa. Adems notifica sobre la


insercin de un nuevo elemento en la ltima posicin (loading view).

dataFnishedLoading() en complemento, la desactiva y remueve el tem.

Y setMoreData() tan solo determina si hay ms datos o no.

Implementar los comportamientos en la vista


Hagamos memoria:

El mtodo showLoadMoreIndicator() muestra el view de cargar ms y


allowMoreData() determina si an hay datos.

Con esto en mente, ya sabemos cmo ligar los mtodos del adaptador:
@Override
public void showLoadMoreIndicator(boolean show) {
if (!show) {
mProductsAdapter.dataFinishedLoading();
} else {
mProductsAdapter.dataStartedLoading();
}
}

@Override
public void allowMoreData(boolean allow) {
mProductsAdapter.setMoreData(allow);
}

Qu tal si corremos la app?

Corramos La App De Una Vez


Finalmente inicia tu emulador o conecta tu dispositivo y presiona Run.

Si todo nos ha salido de maravillas, veremos lo siguiente:

Genial, cierto?
El cascarn tiene vida!

pero qu tal si creamos la API para ver datos reales?

Veamos cmo hacerlo.

Paso #8. Crear Servicio Web REST


Antes de que avances es importante que repases las lecciones sobre servicios web
y el estilo REST que he creado en mi blog:

Servicios Web Con PHP y MySQL

Servicios Web con estilo REST

Consumir Servicio Web REST

La conceptualizacin es importante para poder crear el de App Products.

Ve y chale un vistazo a los artculos, aqu te espero, tomate tu tiempo

listo?

Excelente!

En primer lugar, definamos la arquitectura que usaremos.

Elegir arquitectura del servicio REST


Para este sistema usaremos Modelo-Vista-Controlador (MVC) junto a un patrn
repositorio.
Cmo funciona?

1. La app Android realiza una peticin HTTP con el mtodo GET para pedir
todos los productos

2. El servicio entra en fase de enrutamiento, donde determina que controlador,


vista y modelo debe actuar.

3. El controlador consulta al repositorio por productos

4. El repositorio pide datos a la base de datos MySQL

5. El controlador recibe los registros

6. El controlador le exige a la vista que imprima en formato JSON la respuesta


hacia el cliente

Con eso en mente, preparemos las tareas de programacin

Crear lista de tareas de programacin


Ok, ahora vamos a escribir lo que tenemos que hacer.
Parto del hecho de que ya has instalado tu entorno de pruebas XAMPP o WAMPP
como te menciono en los tutoriales de servicios.

No es el caso?

Clickea AQU para encontrar algn tutorial.

Recuerda seleccionar como directorio de despliegue la carpeta \xampp\htdocs. De lo


contrario el proyecto PHP no se interpretar.

Posteriormente enlistemos que tanto debemos realizar:

Tareas para servicio REST

1. Terminar diseo de bases de datos

2. Implementar base de datos en MySQL

3. Disear las URIs para acceder a productos

4. Disear la representacin JSON de los recursos

5. Definir estructura del proyecto PHP

6. Crear enrutamiento en index.php

7. Implementar vista JSON

8. Implementar controlador de productos

9. Implementar repositorio de productos

10. Implementar fuente de productos MySQL

11. Manejar errores con excepciones de PHP

12. Pruebas con cliente REST

Listo!

No hay nada ms que decir.


Comencemos inmediatamente.

Tarea #1. Diseo de base de datos


Este paso es escueto en App Products.

Ya que tenemos solo una entidad estudiada para los productos, no es necesario
entrar en ms detalles.

Pero ojo:

Recuerda capturar todos los elementos asociados a tus productos.

Puede que tengas categoras asociadas a estos. Promociones, composicin de


varios productos en uno, accesorios, mltiples caractersticas compuestas, etc.

Diagrama ER
En mi caso, este ejemplo solo requiere el uso de un diagrama entidad-relacin para
comprender el modelo de datos:

Tarea #2. Implementar base de datos en MySQL


Ve a la herramienta phpMyAdmin que viene con tu distribucin XAMPP/WAMPP.

Por lo general accedes con la direccin localhost/phpmyadmin en tu navegador.

(Obviamente primero activa los servicios de Apache y MySQL, ya sea en el panel de


administracin o por consola)
Ahora en phpMyAdmin, selecciona en el panel izquierdo la opcin Nueva. Y en el
panel derecho pon como nombre de la base de datos app_products y presiona
Crear.

Lo siguiente es aadir la tabla de productos.

Esto requiere que uses el comando CREATE TABLE en la pestaa SQL relacionada a
app_products.

Segn los atributos que vimos, este sera:


CREATE TABLE product
(
code VARCHAR(32) PRIMARY KEY NOT NULL,
name VARCHAR(128) NOT NULL,
description VARCHAR(255) NOT NULL,
brand VARCHAR(128) NOT NULL,
price DECIMAL(10,2) NOT NULL,
unitsInStock INT(11) NOT NULL,
imageUrl VARCHAR(255)
);

Insertar datos de prueba


Para el ejemplo que estamos estudiando he creado 1000 registros de ejemplo.

1000 productos farmacuticos falsos, que nos ayudarn a ver el comportamiento


del servicio REST y nuestra app Android.

Est ms que claro que poner los comandos INSERT en este tutorial es una locura.

Sin embargo, puedes usar el archivo product.sql que viene en la carpeta del
tutorial.

Luego ubcate en la tabla product y presiona la opcin Importar.

Cuando ests en la screen de importacin, presiona el botn Seleccionar archivo.


Busca el script SQL y confirma su seleccin.

Por ltimo presiona Continuar y espera hasta que se carguen los elementos.

Tarea #3. Disear URIs para productos


Por el momento la semntica es sencilla.

Incluiremos un solo recurso al que le llamaremos products.

(Si lo deseas, usa productos por si tus reglas de nombrado usan el espaol)

Esto, porque solo consultaremos la coleccin sin ms.

Por lo que la la URI quedara as:


GET /products Recupera todos los productos

Por ejemplo, si ussemos el dominio http://api.appproductos.com para ubicar


nuestra API, entonces consultaramos todos los productos de la siguiente manera:

http://api.appproductos.com/v1/products

Tarea #4. Disear presentacin de recursos


Cmo es la estructura JSON que debe presentar las consultas GET de la URI
anterior?

Pensemos:

Si deseo que vengan todos los productos con todas sus columnas, qu debera
hacer?

La respuesta est en el concepto array y atributos JSON alineados con las


propiedades del producto.

Esquema de modelo:
[
{
"code": "50436-1263",
"name": "Gabapentin",
"description": "Maecenas leo odio, condimentum id, luctus nec, molestie.",
"brand": "Gabapentin",
"price": "16.42",
"unitsInStock": 58,
"imageUrl": "file:///android_asset/mock-product.png"
},
{
"code": "61727-307",
"name": "Insomnia Relief",
"description": "Donec ut mauris eget massa tempor convallis.",
"brand": "Insomnia Relief",
"price": "50.36",
"unitsInStock": 55,
"imageUrl": "file:///android_asset/mock-product.png"
}
]

El extracto JSON anterior contiene dos objetos de productos en un array.

Ahora, de la otra mano, tenemos los errores.

Qu tanto estructurarlos?
Necesitamos cdigos de error?

Tan solo un mensaje?

Evaluando la situacin de nuestra app, donde suponemos que es una app privada
para los vendedores de una farmacia. Un solo mensaje bastar para orientarnos.

Esquema de modelo:
{
"message": "No tienes acceso a la API"
}

Usaremos un atributo string llamado message, el cual contiene el mensaje de error


producido en la API.

Tarea #5. Estructura del proyecto PHP

La anterior ilustracin es la jerarqua de directorios para el servicio REST.

La raz es App Products API.

Luego considerando que esta es la primera versin de la API, tenemos otro


directorio llamado v1.

Siguiendo los patrones arquitectnicos que propusimos tendremos que:

controllers Contiene a los controladores

data Contiene los repositorios y fuentes de datos

domain Alberga las entidades de negocio


exceptions Contiene las excepciones personalizadas PHP

http Contiene los componentes HTTP relacionados al servicio REST

views Contiene las vistas

.htaccess Archivo para las reglas de renombrado de archivos

index.php Punto de partida de la API

InjectionContainer.php Clase contenedora de instancias que sern inyectadas manualmente.

Tarea #6. Generar enrutamiento de recursos en


index.php
Llegados a este punto comenzamos la programacin web.

Y el primer paso es crear pretty urls.

Como vimos en mi artculo sobre servicios REST, obviar las extensiones de


archivos a la hora de consultar recursos es una forma sana de mejorar la sintaxis.

.htaccess
Para ello debes crear un archivo .htaccess en la carpeta v1.

Incluye las directivas de reescritura para Apache vistas en el tutorial.

Fjate:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?PATH_INFO=$1 [L,QSA]

Ahora crea tu archivo index.php.

Y define la lgica de enrutamiento.

De qu manera?

Buenopensemos.
Cundo el cliente Android nos enva una peticin, que debe pasar?

Sera algo as:

1. Llega una peticin compuesta de: mtodo HTTP, segmentos de url, cuerpo
(POST, PUT), tipo de contenido, autorizacin, etc.

2. Se extrae el recurso a consultar (primer segmento).

3. Dependiendo del recurso, se crea un controlador, vista y repositorio


asociados.

4. El controlador ejecuta la accin dependiendo del mtodo HTTP.

5. La vista imprime la respuesta obtenida desde el controlador.

Ese es el resumen de las acciones.

Veamos cmo interpretarlas en cdigo PHP.

Las peticiones HTTP


Dentro del directorio http agrega una nueva clase llamada Request (peticin en
espaol).

Segn lo que detallamos, esta tiene varios atributos. Pero por el momento
pondremos solo los que usaremos.

Verbo (mtodo HTTP) y segmentos de la url.

La obtencin de ambos ser en el constructor. Como ya sabes, usa


$_SERVER[REQUEST_METHOD] y $_GET[PATH_INFO] para conseguir sus valores.

class Request
{
public $url_elements;
public $verb;

public function __construct()


{
// Obtener verbo HTTP
$this->verb = $_SERVER['REQUEST_METHOD'];

// No viene ruta definida en la URL?


if (!isset($_GET['PATH_INFO'])) {
return false;
}

// Qu segmentos trae la URL?


$this->url_elements = explode('/', $_GET['PATH_INFO']);

return true;
}

En segundo lugar, vuelve a index.php y crea una nueva peticin.


// Tomamos la peticin entrante
$request = new Request();

Filtrar recurso solicitado


Ya sabes que $url_elements tiene los segmentos de la url y que el primero ser el
recurso ([0]).

As que obtenlo en una variable a parte.


// Se preparan directrices de enrutamiento
$plural_uc_resource_name = ucfirst($request->url_elements[0]);

Crear controlador, vista y repositorio


An no tenemos implementaciones de estos componentes.

El asunto es:

Debemos cargar sus clases en tiempo de ejecucin con el nombre del recurso
extrado.

Cmo hacerlo esto?

Fabrica el nombre de las clases MVC asociadas al recurso.

Esto est ligado a tus reglas de nombrado.

Por ejemplo

Yo llamar a los controladores con la sintaxis *Controller.

Al igual que a los repositorios: *Repository.

Concretamente, el controlador y repositorio de los productos sern


ProductsController y ProductsRepository.

La pillas?
Programticamente lo que tenemos que hacer, es poner en mayscula la primera
letra del recurso y luego concatenarlo a las plantillas.

Observa:
$controller_name = $plural_uc_resource_name . 'Controller';
$repository_name = $plural_uc_resource_name . 'Repository';
$sql_data_source_name = 'Sql' . $plural_uc_resource_name . 'DataSource';

Sabiendo los nombres de las clases, entonces creamos instancias de ellas (si es
que existen).
if (class_exists($controller_name)
&& class_exists($repository_name)
&& class_exists($sql_data_source_name)
) {

// Ahora, ensamblamos la triada MVC


$json_view = new JsonView();
$sql_data_source = new $sql_data_source_name(
InjectionContainer::provideDatabaseInstance());
$repository = new $repository_name($sql_data_source);
$controller = new $controller_name($repository);

Ejecutar accin del controlador


Los controladores manejarn como mnimo los cuatro mtodos principales HTTP:
GET, POST, PUTy DELETE.

Y para ejecutar alguno en tiempo real debemos estandarizar sus accesos.

De qu manera hacerlo?

Respuesta certera: Crear mtodos en el controlador tipo *Action().

Es decir,

postAction(), getAction(), putAction() y deleteAction().

As podremos hacer la concatenacin entre $verb + Action.

Ves?

Apuntamos de forma generalizada a la accin entrante.

De seguro ya sabes que con strtolower() cumplimos el cometido:


$action_name = strtolower($request->verb) . 'Action';
$response = $controller->$action_name($request);
Imprimir la respuesta de la accin
Y para el final, la instancia de la vista JSON imprime la respuesta.
$json_view->render($response);

An no la tenemos ninguna clase implementada.

Por esa razn, iremos al siguiente paso

Tarea #7. Implementar Vista


En primer lugar disearemos la vista a travs de una interfaz que desacople el
comportamiento.

Qu acciones tendr?

Renderizar la respuesta.

As que crea una nueva interfaz llamada View dentro de la carpeta views:
interface View
{
public function render($response);
}

Ahora crearemos una implementacin especfica.

Se trata de JsonView.

Es la vista que mostrar todas las respuestas de nuestra API.

Ella tomar los resultados de nuestros recursos y enviar al cliente la informacin.

Veamos:
class JsonView implements View
{

public function render($response)


{
header('Content-Type: application/json; charset=utf8');
echo json_encode($response->getBody(), JSON_PRETTY_PRINT);
http_response_code($response->getStatus());
return true;
}
}
Con el objeto Response que toma la vista, cercirate de establecer el tipo, el cuerpo y
el cdigo HTTP de respuesta.

Respuestas
Al igual que las peticiones, tambin conceptualizaremos las respuestas en cdigo.

Esta tendr el cuerpo con los datos y el estado HTTP resultante.

En pocas palabras, tendremos un pojo bsico as:


class Response {
private $body;
private $status;

public function getBody() {


return $this->body;
}

public function setBody($body) {


$this->body = $body;
}

public function getStatus() {


return $this->status;
}

public function setStatus($status) {


$this->status = $status;
}

Como vimos hace poco, la vista usar estos elementos para renderizar el resultado
al cliente.

Tarea #8. Implementar controlador de productos


Como dije anteriormente, el controlador procesar las acciones dependiendo del
verbo HTTP.

Para manifestar esta declaracin, creemos una interfaz llamada Controller dentro
de controllers con los cuatro mtodos principales.
interface Controller
{
function getAction($request);

function postAction($request);

function putAction($request);
function deleteAction($request);

El siguiente paso es crear el controlador de los productos.

Aade una clase llamada ProductsController e implementa a Controller.


class ProductsController implements Controller {

Constructor
Luego genera un constructor que reciba el repositorio como parmetro y lo asigne
a una variable miembro predefinida.
private $productsRepository;

public function __construct($productsRepository) {


$this->productsRepository = $productsRepository;
}

getAction()
Enseguida escribe las 4 acciones.
public function getAction($request) {

public function postAction($request) {

public function putAction($request) {

public function deleteAction($request) {

Ya sabes que solo implementaremos getAction(), debido a que es el nico


requerimiento que satisficiremos por el momento.

La pregunta es:

Qu acciones realizamos para obtener los productos?

Llamaremos al repositorio para que nos entregue productos.


El resultado (tanto exitoso como adverso) ser empaqueta en un objeto Response.

Este ser retornado a la vista (output handler) para imprimirlo.


public function getAction($request) {
$response = new Response();

if (isset($request->url_elements[1])) {

throw new ApiException(400, STATUS_CODE_400_MALFORMED);

} else {

$results = $this->productsRepository->getAllProducts();

if (is_array($results)) {
$response->setBody($results);
$response->setStatus(200);
} else if (is_string($results)) {
$response->setBody(['message' => $results]);
$response->setStatus(200);
}

}
return $response;
}

La cosa, es que no tenemos al repositorio todava.

Qu tal si lo creamos?

Tarea #9. Implementar repositorio de productos


Con la aplicacin Android que creamos al inicio desarrollamos el concepto de
repositorio.

Para el servicio web es exactamente lo mismo.

Crearemos un interfaz del repositorio de productos, determinando


comportamientos por cada accin a los datos.

Cules tenemos?

solo una: Consulta de todos los productos.

As que todo se resume a crear la interfaz IProductsRepository y aadir el mtodo


getAllProducts():

interface IProductsRepository
{
public function getAllProducts();
}

Por aadidura, creamos la implementacin del repositorio llamada


ProductsRepository.

Recuerda que este debe comunicarse con la fuente de datos SQL.

En otras palabras:

Define una variable miembro llamada $sqlProductsDataSource y asgnala en el


constructor.

class ProductsRepository implements IProductsRepository


{
private $sqlProductsDataSource;

public function __construct(ProductsDataSource $productsDataSource)


{
$this->sqlProductsDataSource = $productsDataSource;
}

public function getAllProducts()


{
return $this->sqlProductsDataSource->retrieve();
}
}

Notas que dentro de getAllProducts() invocamos el mtodo retrieve() de la


fuente de datos?

Bien, este hace parte de la siguiente definicin:

Tarea #10. Implementar fuente de datos MySQL


Crea dentro de data un directorio llamado datasource.

Aqu pondremos todas las fuentes de datos asociadas a los recursos.

Y el problema es claro.

Usaremos MySQL como fuente de datos y el nico recurso son los productos
(temporalmente).

En esta afirmacin hay varias minitareas escondidas:

Creacin de interfaz de fuente de datos


Implementacin de la fuente de datos

Conexin PDO hacia MySQL

En ese orden de cosas, construyamos.

Crear interfaz de fuente de datos MySQL


Eliminando responsabilidades crearemos una interfaz repositorio-fuente llamada
ProductsDataSource.

Necesitamos obtener todos los productos, as que el nico mtodo ser retrieve().

interface ProductsDataSource {
function retrieve();

Crear fuente de datos MySQL


Crea una nueva clase llamada SqlProductsDataSource e implementa
ProductsDataSource.

class SqlProductsDataSource implements ProductsDataSource {

Siguiendo la misma rutina, pon una variable miembro para la conexin PDO
llamada $dbh (database handler).

Pasa la dependencia en el constructor:


private $dbh;
private $table_name = PRODUCT_TABLE_NAME;

public function __construct(PDO $dbh) {


$this->dbh = $dbh;
}

En tercer lugar, sobrescribe el controlador retrieve():

function retrieve() {
$sql = 'SELECT * FROM ' . $this->table_name;
$stmt = $this->dbh->prepare($sql);
if ($stmt->execute()) {
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
return $stmt->errorInfo()[2];
}
}

Qu fue lo que hice?

Lo comn. Cre un comando $sql con la sentencia SELECT para la tabla productos.

La constante PRODUCT_TABLE_NAME puedes encontrarla en el archivo


datasource/tables.php

Luego prepar la sentencia (prepare()).

Y al final la ejecut (execute()).

Si todo vino bien, entonces retorno un array asociativo con fetchAll().

De lo contrario, retorno un string con el mensaje de error ocurrido (errorInfo()).

Tarea #11. Manejar errores con excepciones de PHP


Resulta que hay errores que podran destruir nuestra respuesta abruptamente.

Por ejemplo, cuando intentamos conectar PDO, al detectar formatos no


compatibles en la URI de la peticin, etc.

Estos son elementos por fuera del comportamiento feliz.

Y esta es la solucin:

Usa las excepciones de PHP para crear respuestas para estos comportamientos.

Si quieres aprender cmo hacerlo, sigue estos pasos:

#1. Crea una clase que extienda de Exception dentro de exceptions. Llmala
ApiException.

#2. Pon un objeto Response como atributo y recibe en el constructor el estado y el


cuerpo.
class ApiException extends Exception {
public $response;

public function __construct($status, $message) {


$this->response = new Response();
$this->response->setStatus($status);
$this->response->setBody(['message' => $message]);
}

#3. Por ltimo, ve a index.php y registra un manejador de excepciones con


set_exception_handler().

Este se encargar de recibir la excepcin, crear una nueva vista JSON e imprimir la
respuesta:
set_exception_handler(function (ApiException $exception) {
$json_view = new JsonView();
$json_view->render($exception->response);
}
);

Carga automtica de recursos construidos


Te has preguntado qu pasa cuando en index.php se intente usar
ProductsController si no tenemos una directiva de importacin definida?

Claramente tendrs un error de acceso a los ficheros.

Ahora

cmo incluirlos en tiempo de ejecucin?

Respuesta concreta:

Autoloaders.

Usaremos la funcin spl_autoload_register() para registrar una funcin que incluya


los archivos dependiendo de su nombre.

El paso a seguir?

Abre index.php y pon la autocarga al principio:

spl_autoload_register('apiAutoload');
function apiAutoload($classname) {
if (preg_match('/[a-zA-Z]+Controller$/', $classname)) {
@include __DIR__ . '/controllers/' . $classname . '.php';
return true;
} elseif (preg_match('/[a-zA-Z]+Repository$/', $classname)) {
@include __DIR__ . '/data/' . $classname . '.php';
return true;
} elseif (preg_match('/[a-zA-Z]+DataSource$/', $classname)) {
@include __DIR__ . '/data/datasource/' . $classname . '.php';
return true;
}

return false;
}

Cuando los controladores, repositorios o fuentes de datos sean intentados de crear


basados en un nombre dinmico, entonces apiAutoload() ser ejecutado.

Dependiendo del string que entra como $classname (nombre de la clase), as mismo
se incluye (include) el archivo.

Este tip y varios de los que te he mostrado han sido influencia de Lorna Jane. Una excelente
desarrolladora de IBM. Tiene lecturas obligadas en su blog y publicaciones.

Contenedor de dependencias
Para crear la conexin PDO, usaremos una clase llamada InjectionContainer.

Bsicamente el objeto PDO ser inicializado con los datos de conexin. Y luego lo
proveeremos a travs de un mtodo.

chale un vistazo:

class InjectionContainer {
private static $pdo = null;

public static function provideDatabaseInstance() {


if (self::$pdo == null) {
try {
self::$pdo = new PDO(
'mysql:dbname=' . DATABASE .
';host=' . HOST . ";",
MYSQL_USER,
MYSQL_PASSWORD,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
);

// Habilitar excepciones
self::$pdo->setAttribute(PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
throw new ApiException(500, STATUS_CODE_500);
}
}

return self::$pdo;

}
}

Los datos de acceso a MySQL estn definidos en las siguientes constantes:

data/datasource/mysql_login.php
<?php
/**
* Provee las constantes para conectarse a la base de data
* Mysql.
*/
define('HOST', 'localhost');// Nombre del host
define('DATABASE', 'app_products'); // Nombre de la base de controllers
define('MYSQL_USER', 'root'); // Nombre del usuario
define('MYSQL_PASSWORD', ''); // Constrasea

Por lo general, el usuario administrador de MySQL es root sin contrasea.

Pero si modificaste las credenciales, debes cambiar los valores aqu.

Ensamblar la triada MVC


Ya para finalizar el desarrollo, ve a index.php y completa la creacin de la vista, el
controlador, la fuente de datos y el repositorio.
// Se preparan directrices de enrutamiento
$plural_uc_resource_name = ucfirst($request->url_elements[0]);
$controller_name = $plural_uc_resource_name . 'Controller';
$repository_name = $plural_uc_resource_name . 'Repository';
$sql_data_source_name = 'Sql' . $plural_uc_resource_name . 'DataSource';

if (class_exists($controller_name)
&& class_exists($repository_name)
&& class_exists($sql_data_source_name)
) {

// Ahora, ensamblamos la triada MVC


$json_view = new JsonView();
$sql_data_source = new $sql_data_source_name(
InjectionContainer::provideDatabaseInstance());
$repository = new $repository_name($sql_data_source);
$controller = new $controller_name($repository);

// Esto nos permitir ejecutar la accin que viene del ciente


$action_name = strtolower($request->verb) . 'Action';
$response = $controller->$action_name($request);

// Y finalmente, mostraremos la respuesta


$json_view->render($response);
} else {
throw new ApiException(400, STATUS_CODE_400_MALFORMED);
}

Uff!

Y ahora stenemos una miniAPI.

Qu tal funcionar?

Vamos a checar

Tarea #12. Testear recuperacin de productos


Ve a tu navegador favorito y teclea la siguiente URL:

http://localhost/api.appproducts.com/v1/products

Si todo va bien, podrs ver los 1000 productos farmacuticos:


Cool, no?

Tambin, puedes probar los errores para ver que tal anda.

Por ejemplo, ver qu sucede si usas POST en el recurso de los productos. Escribir la
URL de forma incorrecta, etc.

Cambiando de tema, en lo que se refiere a la API, hasta el momento todo est


saldado.
(Recuerda, este es solo el nivel 1 del proyecto, obviamente aadiremos ms
caractersticas en los siguientes tutoriales)

Ahora vamos a ver cmo usar la librera Retrofit para construir el cliente REST en
Android.

Paso #9. Crear Cliente HTTP Para


Consumir La API
Llegamos al ltimo paso.

Nuestro servicio REST con PHP est funcionando y nuestra app Android lista para
consumir.

Para crear la implementacin de nuestra fuente de datos en el servidor, usaremos


la librera Retrofit.

Para qu sirve?

Para facilitar peticiones HTTP hacia nuestras APIs.

Se acomoda bastante bien a las URLs que maneja el estilo REST.

Y nos permite usar Gson para convertir las respuestas directamente en objetos de
negocio.

Realmente poderosa!

Como te imaginars, codificaremos menos cdigo para tener rpidamente los


productos en nuestra app.

Convencido a usarla?

Si no es el caso, recuerda que siempre tendrs varias opciones como el cliente


HttpUrlConnection y la Volley.

Iniciemos

Tarea #1. Configurar Retrofit En Android Studio


El primer movimiento es la inclusin de su dependencia en build.gradle.

En este momento mientras escribo este tutorial, Retrofit est en su versin 2.1, as
que tendrs la siguiente lnea:
dependencies {

compile 'com.squareup.retrofit2:retrofit:2.1.0'

Ahora, para aadir el mdulo de agregacin para Gson, usa las siguientes
dependencias:
dependencies {

compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.google.code.gson:gson:2.7'
}

Sincroniza tus archivos de construccin Gradle y estars listo.

Tarea #2. Habilitar permisos de red


Superimportante que nuestra app le pida permisos al sistema para acceder a la
conexin de red.

La forma?

Incluye la siguiente etiqueta <uses-permission> en tu AndroidManifest.xml.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hermosaprogramacion.premium.appproductos">

<uses-permission android:name="android.permission.INTERNET"/>

<application>
...
</application>

</manifest>

Tarea #3. Asegurar una correcta deserializacin


La librera Gson en su momento parsear los objetos JSON que vienen de nuestro
servicio REST a objetos Product.

Cmo evitar que haya errores o malas interpretaciones?

Verifica que tu POJO tenga los mismo atributos (nombre, tipo y cantidad) para
recibir los objetos JSON.
Si recuerdas la entidad para los productos, yo antepuse una m para los nombres de
los campos.

Y es casi infalible que al realizar la conversin, no obtendrs los datos


correctamente, solo por esto.

Cmo arreglarlo?

Usa la anotacin @SerializedName de Gson.

Esta indica al parser, que en el campo con la anotacin debe ir el valor del atributo
JSON.

Por ejemplo, nosotros tendramos que cambiar todos los atributos de la siguiente
manera:
public class Product {
@SerializedName("code")
private String mCode;

@SerializedName("name")
private String mName;

@SerializedName("description")
private String mDescription;

@SerializedName("brand")
private String mBrand;

@SerializedName("price")
private float mPrice;

@SerializedName("unitsInStock")
private int mUnitsInStock;

@SerializedName("imageUrl")
private String mImageUrl;

Tarea #4. Definir endpoints para la API


Lo siguiente es crear una interfaz donde estructuremos los segmentos de las URIs
hacia las cuales enviaremos peticiones.

Por lo tanto, crea un paquete dentro de data llamado api.

En su interior pon una interfaz llamada RestService.

Hasta all, nada del otro mundo.

Lo siguiente ser usar las anotaciones de Retrofit.

Cmo funcionan?
La respuesta sencilla: definen el tipo de respuesta y los componentes de la url sin
ms.

Por ejemplo

Si vamos a usar el mtodo GET, entonces anotamos con @GET la peticin.

Con POST sera @POST

y as sucesivamente.

Teniendo en claro eso, te mostrar como definir la operacin de consulta de los


productos.
public interface ApiEndpoints {
@GET("products")
Call<List<Product>> getProducts();
}

Ves lo sencillo que es?

Call es la invocacin de un mtodo HTTP desde Retrofit, la cual tiene como


objetivo el recurso products anotado con @GET y retornar una lista de objetos
Product.

Tarea #5. Crear Instancia Del Cliente


A continuacin crearemos el cliente REST.

Ve a CloudProductsDataSource y define dos variables miembro: un objeto Retrofit y


otro RestService.

Adicionalmente, incluye una constante para la URL base.


public static final String BASE_URL = "http://10.0.2.2/api.appproducts.com/v1/";
private final Retrofit mRetrofit;
private final RestService mRestService;

IMPORTANTE:

Tu URL base depende del host local. Ten en cuenta que:

Si ests usando hosting propio, pon su dominio.


Ejemplo: http://api.appproductos.com/v1

Si usas un emulador del SDK, pon la IP 10.0.2.2.


Si usas genymotion, pon 10.0.3.2

Si ests usando tu telfono + PC local, deben estar bajo la misma red. As


solo usas la IP del PC en el que te encuentres.

Retomando. El objeto Retrofit es el punto de entrada del cliente REST. Su funcin


es tomar las llamadas Call y procesar su envo.

Para crearlo, ve al constructor y usa su patrn builder.


public CloudProductsDataSource() {

mRetrofit = new Retrofit.Builder()


.baseUrl(BASE_URL_AVD)
.addConverterFactory(GsonConverterFactory.create())
.build();

mRestService = mRetrofit.create(RestService.class);
}

Con baseUrl() pon el url definida.

Y aade un conversor Gson con addConverterFactory().

De paso, crea la instancia de RestService con la ayuda del cliente, a travs del
mtodo create().

Tarea #6. Ejecutar la peticin


Posteriormente, enviaremos la peticin al servidor.

La forma de hacerlo es con los mtodos Call.execute() y Call.enqueue().

El primero ejecuta la peticin en el hilo principal.

El segundo lo hace asncronamente y notifica su resultado con la interfaz


Callback<T>.

Cul elegir?

enqueue()

Es as, porque evitar que el hilo de UI se entorpezca y Android nos arroje dilogos
ANR.

Con esta idea en la cabeza, ve a getProducts() y sobrescribe su contenido as:


@Override
public void getProducts(final ProductServiceCallback callback,
ProductCriteria criteria) {

Call<List<Product>> call = mRestService.getProducts();

call.enqueue(new Callback<List<Product>>() {
@Override
public void onResponse(Call<List<Product>> call,
Response<List<Product>> response) {
// Procesamos los posibles casos
processGetProductsResponse(response, callback);

@Override
public void onFailure(Call<List<Product>> call, Throwable t) {
callback.onError(t.getMessage());
}
});
}

Como ves, primero llamamos a RestService.getProducts() para apilar la peticin en


call.

Luego llamamos a enqueue() y tomamos su resultado con una interfaz annima.

onResponse() retorna la respuesta en su parmetro response. Si fue exitosa


(isSuccesfull()), entonces retornamos el contenido (response.body()).

De lo contrario en onFailure(), notificamos el error.

Para procesar la respuesta, podemos aislar el comportamiento en un mtodo


llamado processGetProductsResponse() de la siguiente forma:

private void processGetProductsResponse(Response<List<Product>> response,


ProductServiceCallback callback) {
String error = "Ha ocurrido un error";
ResponseBody errorBody = response.errorBody();

// Hubo un error?
if (errorBody != null) {
// Fu de la API?
if (errorBody.contentType().subtype().equals("json")) {
try {
// Parseamos el objeto
ErrorResponse er = new Gson()
.fromJson(errorBody.string(), ErrorResponse.class);
error = er.getMessage();
} catch (IOException e) {
e.printStackTrace();
}
}

callback.onError(error);
}

// LLegaron los productos sanos y salvos?


if (response.isSuccessful()) {
callback.onLoaded(response.body());
}

El papel de Gson: No hicimos un parsing de JSON a objetos Product explicito, ya


que el mdulo de Retrofit se encarga de ello.

Respuesta de error: Crea una clase llamada ErrorResponse con un atributo string
para capturar las respuestas de error que diseos en la API.

public class ErrorResponse {


@SerializedName("message")
String mMessage;

public String getMessage() {


return mMessage;
}
}

Recuerda que el mtodo Gson.from() parsea un flujo JSON en un objeto cuya clase
pones como parmetro.

Pon A Prueba T App Productos


Para finalizar, levanta los servicios de Apache y MySQL, ejecuta a App Products y
visualiza los resultados.

Qu ves?

Valida todos los comportamientos vistos en el diseo de wireframes.

Swipe to refresh

Endless scroll

Empty state

Errores

El terminado final en la UI tendra el siguiente aspecto:


Prubala lo ms que puedas y ajstala a tus necesidades

Qu Tal Te Fue?
Te pareci ms ordenado el uso del patrn MVP en Android?

Y qu me dices de la simplificacin del consumo del servicio web con Retrofit?

Chvere, cierto?

Cuando cre este tutorial quera ayudarte a:

Ordenar la forma en que escribes tus apps


Seguir unos pasos lgicos para desarrollar

Repasar la creacin de servicios REST con un ejemplo ms popular

Empezar a incluir libreras como Retrofit para ahorrar tiempo y paz mental

Comenzar una app completa que te sirva de ejercicio, proyecto de grado,


emprendimiento o incluso como plantilla para tu trabajo.

Aspiro a que mi esfuerzo te haya permitido alcanzar al menos uno de ellos.

Qu vendr En El Nivel #2?


Mantente atento!

Ya que estaba contemplando incluir en la segunda etapa lo siguiente:

Autorizacin correo/contrasea en la API

Uso de Dagger

Implementar servicio web con Laravel/Slim

Uso de servicio de hosting/cloud computing real

Inclusin de Sync Adapter para control de casos de sincronizacin

Pantalla detalle del producto

Pantalla de login

Creacin de fuente de datos local con SQLite

Filtros

Bsqueda

Uso del patrn master-detail

Finalmente
estos son los pasos a seguir:

Comenta que te pareci este tutorial AQU. Djame saber a m y a los dems
lectores de la comunidad:

Cmo te benefici en tus conocimientos?,


Qu tanto te servir en tu proyecto actual?
Y si vali la pena invertir tu dinero en mi contenido?

Si has encontrado un error de redaccin, o si tienes una sugerencia de


didctica, o algn error de compilacin/importacin/configuracin en
Android Studio, hzmelo saber de inmediato a
[email protected].

Sabiendo cual es el rumbo de App Products, piensa en requerimientos ms


precisos que puedas sugerirme para crear el nivel #2. Me tomar el tiempo
para evaluar si caben dentro del flujo pensado para esta serie de tutoriales y
en consecuencia los incorporar.

Date una palmadita en la espalda. Leste un tutorial de 103 pginas :)

Saludos,
James

También podría gustarte