Android – Retrofit y Seeeduino Cloud

image_pdf
android - retrofit

android – retrofit

 

Introducción

Y con este post llegamos a una de la librerías más útiles. Retrofit nos facilita el uso de llamadas rest, realizando las llamadas asíncronas sin que nosotros nos preocupemos de nada. Si además usamos GSON podemos obtener el resultado en una colección de objetos. La utilización de Retrofit en nuestros proyectos es muy fácil y ofrece un buen rendimiento. Una vez más, Square nos brinda una gran librería (ya hemos hablado anteriormente de Picasso).

Este post es una continuación de los de seeeduino (Seeduino Cloud – Parte 1 , Seeeduino Cloud – Parte 2). Al finalizarlo podremos controlar nuestro ventilador des de una aplicación Android.

  • Creando la estructura del proyecto
  • Configuración
  • Creando nuestras clases Pojo
  • Creando una instancia de Retrofit
  • Preparando end points
  • Preparando nuestro activity

 

Creando el proyecto y su estructura

Crearemos un proyecto con un empty activity

android_new_project

A continuación crearemos una nueva estructura de packages. Esto es una costumbre, no es ningún estándar y cada developer utiliza la que más le conviene o resulta más fácil de usar, para organizar su código. Muy probablemente, cambiará durante el proceso desarrollo y aquí cada unos tiene sus preferencias. Lo que realmente es importante es utilizar una que nos ayude mantener nuestro código debidamente organizado.

package_structure

Configuración

Añadiremos el plugin android-apt a nuestro classpath en el fichero build.gradle. Este se encuentra en la raíz de nuestro proyecto.

dependencies{
  classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

Dentro del archivo app/build.gradle, debemos añadir las dependencias de Retrofit.

apply plugin : 'com.neenbedankt.android-apt'

dependencies {
 
  // retrofit, gson
  compile 'com.google.code.gson:gson:2.6.2'
  compile 'com.squareup.retrofit2:retrofit:2.0.2'
  compile 'com.squareup.retrofit2:converter-gson:2.0.2'

  // picasso
  compile 'com.squareup.picasso:picasso:2.5.2'
  compile 'jp.wasabeef:picasso-transformations:2.1.0'

  // butterknife
  compile 'com.jakewharton:butterknife:8.4.0'
  apt 'com.jakewharton:butterknife-compiler:8.4.0'
}

Seguidamente añadiremos el permiso de red y de comprobación de esta a nuestro archivo androidmanifest.xml

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

 

Creando nuestra clase Pojo

Esta es la clase base sobre la que Gson creará la instancia con los resultados de la llamada rest realizada con Retrofit, colocaremos la clase en el package model. Una de las maneras más fáciles de generar nuestras clases es utilizar un generador. Por ejemplo, jsonschema2pojo. Pero debemos tener cuidado, podemos encontrarnos fácilmente con una respuesta Json inmensa de la cual tan solo necesitamos alguna información concreta.

package com.adictosalainformatica.fanmanager.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class FanModel {

    @SerializedName("pin")
    @Expose
    private Integer pin;
    @SerializedName("status")
    @Expose
    private Integer status;

    /**
     *
     * @return
     * The pin
     */
    public Integer getPin() {
        return pin;
    }

    /**
     *
     * @param pin
     * The pin
     */
    public void setPin(Integer pin) {
        this.pin = pin;
    }

    /**
     *
     * @return
     * The status
     */
    public Integer getStatus() {
        return status;
    }

    /**
     *
     * @param status
     * The status
     */
    public void setStatus(Integer status) {
        this.status = status;
    }

}

Es importante no acabar con una colección de clases inmensa llena de getters y setters que no vamos a utilizar. Todo ello para mantener nuestro código limpio o no acabar encontrándonos el error «64k method limit in dex». No es raro encontrarnos con él si utilizamos muchas librerías y colecciones de clases generadas a partir del resultado de una respuesta Json desmesurada. Podemos fácilmente solventar el problema revisando nuestras clases Pojo y eliminando todas aquellas variables y clases que no vamos a utilizar e incluso plantearnos si todas las librerías que estamos utilizando son realmente necesarias. Pero si esto no fuera suficiente, podemos solventar el problema con multidex a costa de perder la funcionalidad de instant run.

 

Creando una instancia de Retrofit

Para enviar solicitudes de red a una API, tenemos que utilizar la clase Retrofit.Builder y especificar la URL base para el servicio. Por lo tanto, crearemos una clase llamada ApiClient.java bajo en el package rest.

BASE_URL – es la url base de nuestra API. Vamos a utilizar esta url para todas las solicitudes posteriores.

package com.adictosalainformatica.fanmanager.rest;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;


public class ApiClient {

    public static final String BASE_URL = "http://192.168.1.33/arduino/";
    private static Retrofit retrofit = null;


    public static Retrofit getClient() {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

 

 

Preparando end points

Los end points se definen dentro de una interfaz mediante anotaciones especiales de Retrofit para codificar información sobre los parámetros y el tipo de petición. Además, el valor de retorno es siempre una llamada con parámetros <T>, en nuestro caso <FanModel>. Crearemos la interface ApiInterface.java en el package rest

package com.adictosalainformatica.fanmanager.rest;

import com.emotionexperience.fragancemanager.model.FraganceModel;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;


public interface ApiInterface {
    @GET("digital/{pin}/{value}")
    Call setPin(@Path("pin") int pin, @Path("value") int value);

    @GET("status/{pin}")
    Call getStatus(@Path("pin") int pin);
}

 

Preparando nuestro activity

A continuación mostramos el código de nuestro activity que se encuentra en el package ui. Como se puede observar utilizamos Picasso y Butterknife

package com.adictosalainformatica.fanmanager.ui;

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ImageButton;
import android.widget.Toast;

import com.adictosalainformatica.fanmanager.R;
import com.adictosalainformatica.fanmanager.model.FanModel;
import com.adictosalainformatica.fanmanager.rest.ApiClient;
import com.adictosalainformatica.fanmanager.rest.ApiInterface;
import com.squareup.picasso.Picasso;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import jp.wasabeef.picasso.transformations.ColorFilterTransformation;
import jp.wasabeef.picasso.transformations.CropCircleTransformation;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.main_btn_fan)
    ImageButton btnFan;

    // Constants
    private static final String TAG = MainActivity.class.getName();
    private static int PIN = 8;

    private int fanStatus = 0;
    private static ApiInterface apiService;

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

        ButterKnife.bind(this);

        Picasso
                .with(getApplicationContext())
                .load(R.drawable.fan_image)
                .transform(new CropCircleTransformation())

                .into(btnFan);

        apiService = ApiClient.getClient().create(ApiInterface.class);

        if(isConetctionEnabled(getApplicationContext())){
            getFanStatus(PIN);
        }else{
            Toast.makeText(getApplicationContext(),"Internet connection failed",Toast.LENGTH_LONG).show();
        }
    }

    @OnClick(R.id.main_btn_fan)
    public void switchFanStatus() {
        if(isConetctionEnabled(getApplicationContext())){
            if(fanStatus == 0){
                setFanStatus(PIN,1);
            }else{
                setFanStatus(PIN, 0);
            }
        }else{
            Toast.makeText(getApplicationContext(),"Internet connection failed",Toast.LENGTH_LONG).show();
        }
    }

    /**
     * Get current Fan status
     * @param pin
     */
    private void getFanStatus(int pin) {
        Call <FanModel> call = apiService.getStatus(pin);
        call.enqueue(new Callback<FanModel>() {
            @Override
            public void onResponse(Call<FanModel> call, Response<FanModel> response) {
                if (response.isSuccessful()) {
                    fanStatus =  response.body().getStatus();
                    Log.d(TAG, "Fan status: " + fanStatus);

                    if(fanStatus == 0){
                        int color = Color.parseColor("#33ee092b");
                        setColorFilter(color);

                    }else{
                        int color = Color.parseColor("#3300ff80");
                        setColorFilter(color);
                    }
                } else {
                    //request not successful (like 400,401,403 etc)
                    Log.e(TAG,response.message());
                }
            }

            @Override
            public void onFailure(Call<FanModel> call, Throwable t) {
                // Log error here since request failed
                Log.e(TAG, "Error: " + t.toString());
            }
        });
    }

    /**
     * Set Fan status
     * @param pin
     */
    private void setFanStatus(int pin, int status){
        Call call = apiService.setPin(pin,status);
        call.enqueue(new Callback<FanModel>() {
            @Override
            public void onResponse(Call<FanModel> call, Response<FanModel> response) {
                if (response.isSuccessful()) {
                    fanStatus =  response.body().getStatus();
                    Log.d(TAG, "Fan status: " + fanStatus);

                    if(fanStatus == 0){
                        int color = Color.parseColor("#33ee092b");
                        setColorFilter(color);

                    }else{
                        int color = Color.parseColor("#3300ff80");
                        setColorFilter(color);
                    }
                } else {
                    //request not successful (like 400,401,403 etc);
                    Log.e(TAG,response.message());
                }
            }

            @Override
            public void onFailure(Call<FanModel> call, Throwable t) {
                // Log error here since request failed
                Log.e(TAG, "Error: " + t.toString());
            }
        });
    }

    /**
     * Set image filter color
     * @param color
     */
    private void setColorFilter(int color){
        Picasso
                .with(getApplicationContext())
                .load(R.drawable.fan_image)
                .transform(new ColorFilterTransformation(color))
                .transform(new CropCircleTransformation())
                .into(btnFan);
    }

    /**
     * Tests if there's connection
     * @param cx context application
     * @return true or false      
     */
    public static boolean isConetctionEnabled(Context cx){
        ConnectivityManager conMgr =  (ConnectivityManager)cx.getSystemService(Context.CONNECTIVITY_SERVICE);

        if (conMgr.getActiveNetworkInfo() != null
                && conMgr.getActiveNetworkInfo().isAvailable()
                && conMgr.getActiveNetworkInfo().isConnected()) {
            return true;
        } else {
            return false;
        }
    }
}

I finalmente, nuestro layout

<?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/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.adictosalainformatica.fanmanager.ui.MainActivity">


    <ImageButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/fan_image"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/main_btn_fan"
        android:background="@null"
        android:padding="10dp"/>
</RelativeLayout>

Desde este enlace os podéis descargar fan_image

Observaciones

Después de mucho tiempo lidiando con AsyncTask, rotaciones, memory leaks… Retrofit nos permite abstraernos de todo esto y además es muy fácil de utilizar. Por todo ello, Retrofit se convierte en una librería casi indispensable. Finalmente, dejo el enlace a la web de Retrofit y un ejemplo en Github. El cual, nos permite apagar y encender un ventilador siempre y cuando tengamos nuestro Seeduino Cloud configurado y preparado para trabajar con un relayshield.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.