Browse Category

Desarrollo

soap_pattern

Integración Retrofit y Servicios SOAP

Conocer como se integran servicios web en aplicaciones móviles es un “Must” que todo desarrollador debe conocer. La mayoría de sistemas han adoptado los servicios Web del tipo REST / JSON, pero aún existen sistemas, que por antigüedad o por integración con otros servicios, deben utilizar servicios del tipo SOAP/WSDL. A lo largo de los años, un desarrollador tocará ambos tipos de servicios web, por lo que es muy recomendable saber como se introducen en nuestros proyectos.

 


SOAP vs REST

No voy a entrar en el debate de cual es mejor, ya que ambos hacen la función que tienen. Son tecnologías que son complementarias, aunque no es normal encontrarse las dos en un mismo proyecto.

Algunas de las características más importantes son:

  • Los servicios SOAP funcionan con un fichero de definición del servicio (WSDL), en el que se definen los tipos de datos que se usarán, los métodos que tiene el servicio, y las respuestas. Por el contrario, una petición REST no tiene este tipo de fichero, por lo que tendremos que saber de antemano el nombre de los métodos, y el tipo de datos que se le pasa.
  • Las servicios REST suelen ser más rápidos que los servicios SOAP. Esto es debido a la estructura de los datos. JSON es más ligero, mientras que el XML que se genera en un servicio SOAP es mucho mas pesado. En servicios SOAP hay que mandar cabeceras y “namespaces” (lo que es en desarrollo no tienen ninguna importancia), que en un JSON no aparecen.
  • Los servicios REST, al ser más ágiles, se han creado más librerías para dispositivos móviles (Gson, Jackson, Moshi, … ) y mas facilidad de integración, mientras que para los servicios SOAP no hay tal abanico de oportunidades (Ksoap2, SimpleXML).

Ejemplo práctico

Para el ejemplo que vamos a realizar, utilizaremos un servicio web gratuito de WebServiceX, el cual nos ofrecerá un listado de ciudades de Estados Unidos con sus códigos postales a través del nombre de la ciudad, que será lo que le pasaremos al servicio.

Definición del Servicio

La petición tendrá el siguiente formato:

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetInfoByCity xmlns="http://www.webserviceX.NET">
      <USCity>string</USCity>
    </GetInfoByCity>
  </soap12:Body>
</soap12:Envelope>

Mientras, la respuesta tendrá el siguiente formato:

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetInfoByCityResponse xmlns="http://www.webserviceX.NET">
      <GetInfoByCityResult>xml</GetInfoByCityResult>
    </GetInfoByCityResponse>
  </soap12:Body>
</soap12:Envelope>

Para probar el servicio antes del desarrollo, podremos hacerlo simulando una petición POST con alguna herramienta de servicios. En mi caso, suelo usar POSTMAN.

soap_postman

Gracias al uso de esta herramienta, podemos observar la salida del webservice, así poder crear el objeto de la respuesta correcto.

Librerías

Utilizaremos las librerías de retrofit y okHttp para las peticiones. Ademas utilizaremos el convertidor SimpleXml para retrofit

compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.3.1'

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile ('com.squareup.retrofit2:converter-simplexml:2.1.0'){
    exclude group: 'stax', module: 'stax-api'
    exclude group: 'stax', module: 'stax'
    exclude group: 'xpp3', module: 'xpp3'
}

Configuración de Retrofit y OkHttp

La creación de la instancia de Retrofit tendrá que llevar consigo el Converter de SimpleXml con el interceptor necesario:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

Strategy strategy = new AnnotationStrategy();

Serializer serializer = new Persister(strategy);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .connectTimeout(2, TimeUnit.MINUTES)
            .writeTimeout(2, TimeUnit.MINUTES)
            .readTimeout(2, TimeUnit.MINUTES)
            .build();

Retrofit retrofit =  new Retrofit.Builder()
    .addConverterFactory(SimpleXmlConverterFactory.create(serializer))
    .baseUrl("http://www.webservicex.net/")
    .client(okHttpClient)
    .build();

Creación del objeto API

Será la interfaz que se utilizará Retrofit para la creación de la API.

public interface UsStatesApi {

    @Headers({
            "Content-Type: text/xml",
            "Accept-Charset: utf-8"
    })
    @POST("/uszip.asmx")
    Call<UsStatesResponseEnvelope> requestStateInfo(@Body UsStatesRequestEnvelope body);

}

Creación de las clases para la request y response

Los objetos tanto de la petición como la respuesta tienen la misma forma de creación, por lo que solo pondremos aquí los de la petición.

@Root(name = "soap12:Envelope")
@NamespaceList({
        @Namespace( prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance"),
        @Namespace( prefix = "xsd", reference = "http://www.w3.org/2001/XMLSchema"),
        @Namespace( prefix = "soap12", reference = "http://www.w3.org/2003/05/soap-envelope")
})
public class UsStatesRequestEnvelope {

    @Element(name = "soap12:Body", required = false)
    private UsStatesRequestBody body;

    public UsStatesRequestBody getBody() {
        return body;
    }

    public void setBody(UsStatesRequestBody body) {
        this.body = body;
    }
}
@Root(name = "soap12:Body", strict = false)
public class UsStatesRequestBody {

    @Element(name = "GetInfoByCity",required = false)
    private UsStatesRequestData usStatesRequestData;

    public UsStatesRequestData getUsStatesRequestData() {
        return usStatesRequestData;
    }

    public void setUsStatesRequestData(UsStatesRequestData usStatesRequestData) {
        this.usStatesRequestData = usStatesRequestData;
    }

}
@Root(name = "GetInfoByState", strict = false)
@Namespace(reference = "http://www.webserviceX.NET")
public class UsStatesRequestData {

    @Element(name = "USCity", required = false)
    private String city;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

}

Por último, para hacer la petición deberemos hacer lo siguiente:

UsStatesRequestEnvelope envelope = new UsStatesRequestEnvelope();

UsStatesRequestBody body = new UsStatesRequestBody();

UsStatesRequestData data = new UsStatesRequestData();

data.setCity( cityName );

body.setUsStatesRequestData(data);

envelope.setBody(body);

Call<UsStatesResponseEnvelope> call = usStatesApi.requestStateInfo(envelope);

call.enqueue(new retrofit2.Callback<UsStatesResponseEnvelope>() {

    @Override
    public void onResponse(Call<UsStatesResponseEnvelope> call, final Response<UsStatesResponseEnvelope> response) {

       //DO WHAT YOU WANT 

    }

    @Override
    public void onFailure(Call<UsStatesResponseEnvelope> call, Throwable t) {
 
        //ERROR!!
                
    }

});

 

Puedes ver un ejemplo completo en GITHUB

vector_drawables

Android Support Library (III) – VectorDrawables

Una de los principales inconvenientes de la fragmentación de dispositivos que existe en Android, es el uso de imágenes. Tienes que tener tantas imágenes como resoluciones quieras utilizar, haciendo que la aplicación sea más pesada ya que el número de imágenes se triplica o cuadriplica. Gracias a las nuevas versiones de las librerías de soporte podemos utilizar los ficheros SVG.


¿Qué es SVG?

(Scalable Vector Graphics o Gráficos Vectoriales Redimensionables)
Son ficheros xml que contienen la información para pintar una imagen. Estos ficheros nacieron en 1996 y en el 2001 en una recomendación de la W3C. Una de las grandes diferencias que podemos encontrar es que no se pixelan, es decir, por mucho que manipules el fichero, la imagen no se verá distorsionada.

vector_vs_png
Diferencia entre SVG y PNG

 


Uso de imágenes vectoriales en Android

Para comenzar, tendremos que añadir la librería de soporte si no la tuviéramos añadida y además incluir unas líneas en el fichero “build.gradle” de la aplicación para poder utilizar este tipo de imágenes.

Si utilizamos gradle 2.0 o superior:

defaultConfig {
    ...
    vectorDrawables.useSupportLibrary = true
    ...
}

Si utilizamos una versión anterior:

android {  
    defaultConfig {
        generatedDensities = []  
    }  
    
    aaptOptions {  
        additionalParameters "--no-version-vectors"  
    }  

}  

El siguiente paso que tenemos que dar es la creación del fichero que contendrá nuestra imagen vectorial. Android no utiliza los ficheros “SVG” sino una conversión de ellos, VectorDrawables, los cuales son ficheros “XML” con los datos de la imagen. Para crear estos ficheros desde un “SVG” tenemos varias opciones.

  • Herramienta online de conversión de SVG a VectorDrawable. http://inloop.github.io/svg2android/
  • Dentro de Android Studio tenemos la posibilidad de crear nuestros VectorDrawables desde un fichero SVG. Para ello tenemos que ir a “New”->”Vector Asset” y seguir los pasos del wizard.

También podemos obtener los ficheros xml de páginas web. En el caso del ejemplo que voy a mostrar, he utilizado recursos de https://materialdesignicons.com/, ya que ofrece descargar el fichero en el formato que deseemos. Una vez obtenido el fichero e insertado en nuestro directorio “res->drawable”, tan solo tenemos que inicializar el ImageView de esta manera.

<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/android_vd"/>

Animar un vectorial en Android

Animar una imagen vectorial es bastante sencillo. Tenemos que tener 3 elementos:

  • Fichero que contenga la animación del vectorial dentro del directorio “res->animator”.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

        <objectAnimator android:duration="250" android:propertyName="translateY" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="0" android:valueTo="-5" android:valueType="floatType" />

</set>
  • Fichero de la imagen vectorial. En el deberemos indicar un grupo que será el que queramos animar.
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24">

        <group android:name="element">

                <path android:fillColor="#000" android:pathData="M15,5H14V4H15M10,5H9V4H10M15.53,2.16L16.84,0.85C17.03,0.66 17.03,0.34 16.84,0.14C16.64,-0.05 16.32,-0.05 16.13,0.14L14.65,1.62C13.85,1.23 12.95,1 12,1C11.04,1 10.14,1.23 9.34,1.63L7.85,0.14C7.66,-0.05 7.34,-0.05 7.15,0.14C6.95,0.34 6.95,0.66 7.15,0.85L8.46,2.16C6.97,3.26 6,5 6,7H18C18,5 17,3.25 15.53,2.16M20.5,8A1.5,1.5 0 0,0 19,9.5V16.5A1.5,1.5 0 0,0 20.5,18A1.5,1.5 0 0,0 22,16.5V9.5A1.5,1.5 0 0,0 20.5,8M3.5,8A1.5,1.5 0 0,0 2,9.5V16.5A1.5,1.5 0 0,0 3.5,18A1.5,1.5 0 0,0 5,16.5V9.5A1.5,1.5 0 0,0 3.5,8M6,18A1,1 0 0,0 7,19H8V22.5A1.5,1.5 0 0,0 9.5,24A1.5,1.5 0 0,0 11,22.5V19H13V22.5A1.5,1.5 0 0,0 14.5,24A1.5,1.5 0 0,0 16,22.5V19H17A1,1 0 0,0 18,18V8H6V18Z"/>

        </group>
</vector>
  • Fichero que haga de enlace entre la imagen vectorial y la animación. Éste será nuestro vector-drawable. En él indicaremos el grupo que queremos animar de la imagen vectorial y que animación queremos darle.
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/android_vd">

    <target android:name="element" android:animation="@animator/pulse_top"/>

</animated-vector>

 

Para utilizarlo tendremos que indicar al ImageView el atributo app:srcCompat como hicimos anteriormente. Para comenzar la animación, tendremos que recoger la imagen desde código y comenzar la animación.

ivAnimated = (ImageView) findViewById(...);
Drawable drawable = ivAnimated.getDrawable();
if (drawable instanceof Animatable) {
    ((Animatable) drawable).start();
}

Puedes ver un ejemplo completo en GITHUB

patron_II

Patrones de diseño en Android (II): Patrones estructurales

Patrones de diseño en Android (I) : Patrones de creación
Patrones de diseño en Android (II): Patrones estructurales

Siguiendo la línea de patrones de diseño que comenzamos con los patrones de creación, seguimos con los patrones estructurales más utilizados en el desarrollo mobile. Éstos son: Adapter, Facade, y Proxy.

Adapter

El patrón adaptador consiste en crear una clase intermedia para poder ajustar el resultado a una salida esperada. Pongamos un pequeño ejemplo. Imaginaos que tenemos una interfaz(“Phone”) que define los métodos que deben implementar todos los teléfonos.

/**
 * Interface that defines the methods for every phone.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public interface Phone {

    /**
     * Dialing numbers.
     */
    void dialNumbers();

    /**
     * Make a phone call.
     */
    void makeCall();

}

En 1980 existían solo teléfonos fijos “LandlinePhone”, los cuales implementaban estos métodos sin problema.

/**
 * LandlinePhone entity.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class LandlinePhone implements Phone{

    @Override
    public void dialNumbers() {
        System.out.println("Dialing numbers from landline phone");
    }

    @Override
    public void makeCall() {
        System.out.println("Making phone call from landline phone");
    }
}

A principios del año 2000 aparecen los teléfonos móviles los cuales tienen otros métodos diferentes a los definidos en la interfaz.

/**
 * Smartphone entity.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class SmartPhone {

    public void touchScreenForNumbers(){
        System.out.println("Dialing numbers from smartphone");
    }

    public void makePhoneCall(){
        System.out.println("Making phone call from smartphone");
    }

}

Podremos crear una clase “Wrapper” , la cual adaptará los métodos nuevos a los antiguos.

/**
 * Wrapper from {@link SmartPhone} to {@link Phone}.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class SmartphoneWrapper implements Phone{

    private SmartPhone smartPhone = new SmartPhone();

    @Override
    public void dialNumbers() {
        smartPhone.touchScreenForNumbers();
    }

    @Override
    public void makeCall() {
        smartPhone.makePhoneCall();
    }
}

 


Facade

El patrón “Facade” o “Fachada” tiene como objetivo hacer que la comunicación entre dos elementos sea mas sencilla gracias al uso de interfaces, haciendo invisible ciertas partes que pudieran ser más complejas. Pongamos un ejemplo:
Tenemos un sistema en el cual se pueden dar de alta usuarios con su nombre junto con su password. Pero hay ciertos campos que al que va a hacer el registro no le interesan (fecha del registro, sistema,etc..), por lo que al crear una fachada para este proceso, facilitaremos el proceso.

/**
 * User entity.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class User {

    private String username;

    private String password;

    private String system;

    private Date registrationDate;

    public User() {
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getRegistrationDate() {
        return registrationDate;
    }

    public void setRegistrationDate(Date registrationDate) {
        this.registrationDate = registrationDate;
    }

    public String getSystem() {
        return system;
    }

    public void setSystem(String system) {
        this.system = system;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
/**
 * Facade for registration process.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class RegisterFacade {

    private RegistrationManager registrationManager;

    public RegisterFacade() {

        registrationManager = new RegistrationManager();

    }

    public void registerUser(String username, String password){

        User user = new User();

        user.setUsername( username );

        user.setPassword( password );

        user.setRegistrationDate( new Date() );

        user.setSystem("MacOS X - 10.10.5");

        registrationManager.makeRegistry( user );

    }

}
/**
 * Registration manager.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 25/4/16.
 */
public class RegistrationManager {

    public void makeRegistry(User user) {

        SimpleDateFormat sdf = new SimpleDateFormat("dd/mm/yyyy");

        System.out.println(
                "We'll made the registration for user with data: "
                + "\n\tUsername:\t" + user.getUsername()
                + "\n\tPassword:\t" + user.getPassword()
                + "\n\tSystem:\t" + user.getSystem()
                +" \n\tRegistration date:\t" + sdf.format(user.getRegistrationDate())
        );

        // We will make the registration in our system here.Database, rest services,etc...

    }

}

 


Proxy

En informática, el término “proxy” se utiliza en multitud de ocasiones. Este patrón se define como “Proporcionar un representante o sustituto de otro objeto para controlar el acceso a éste con el objetivo de retrasar o controlar y retrasar el coste de inicialización de objetos”.

  • proxy remoto
  • proxy virtual
  • proxy de protección

Pongamos un ejemplo: Tenemos que realizar scrapping de una web. El proceso de conexión y obtención del código de esa web puede ser un proceso muy pesado, por lo que nos interesa poder guardar la referencia de esos datos ya guardados.

Tenemos una interfaz WebParser que define el método de acceso al código de la web.

/**
 * WebParser interface.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 9/5/16.
 */
public interface WebParser {
    String getHtmlContent();
}

Por otra parte, tenemos una clase HtmlWebParser que implementa WebParser, la cual realiza las operaciones necesarias para obtener los datos de la página web que necesitemos

/**
 * HtmlWebParser class.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 9/5/16.
 */
public class HtmlWebParser implements WebParser {

    private String content;

    private String websiteUrl;

    public HtmlWebParser(String url) {

        websiteUrl = url;

        //Here, we would make the connection to the website, making all the heavy transactions here.

        content = "
Website html content
";

    }

    @Override
    public String getHtmlContent() {

        return content;

    }
}

Por último, tendremos una clase HtmlWebParserProxy también implementa la misma interfaz, pero guarda una instancia de un objeto HtmlWebParser para así evitarnos crear más objetos.

/**
 * Proxy class for html web parser.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 9/5/16.
 */
public class HtmlWebParserProxy implements WebParser {

    private HtmlWebParser connection;

    private String websiteUrl;

    public HtmlWebParserProxy(String url) {

        this.websiteUrl = url;

    }

    @Override
    public String getHtmlContent() {

        if (connection == null) {

            System.out.println("New instance of HtmlWebParser");

            connection = new HtmlWebParser(websiteUrl);

        }

        return connection.getHtmlContent();

    }
}

Una vez hecho esto, podremos utilizar nuestra clase proxy de la siguiente manera.

WebParser connection = new HtmlWebParserProxy("http://blog.asanchez-portfolio.es");
System.out.println(connection.getHtmlContent());
System.out.println(connection.getHtmlContent());

En la salida por consola podremos observar que solo se crea una instancia con lo que el código solo se descargará una vez :

...
New instance of HtmlWebParser

Website html content

Website html content
...

 Puedes ver el código completo en GITHUB

Animations

Android Support Library (II) – Transiciones entre actividades y fragments

Desde la versión lollipop de android, se ha dado mucha importancia a las animaciones para mostrar diferentes comportamientos o para dirigir al usuario a nuevas pantallas. Con la llegada de Material Design, se ha incrementado el número de animaciones, como el compartimiento de elementos visuales entre diferentes vistas. Gracias a las librerías de compatibilidad,  la tarea se ha simplificado considerablemente, dándonos la oportunidad de realizar aplicaciones más atractivas para el usuario final.  La librería de compatibilidad nos ofrece clases de utilidades para que estas transiciones se puedan hacer, sin tener en cuenta las diferentes versiones de android. Por ejemplo: En las versiones anteriores a la 5, las transiciones entre actividades no se mostrarán, pero en las versiones posteriores si que lo harán.

 

Transiciones entre Actividades

Con la incursión de material design, las transiciones y el compartimiento de elementos visuales entre actividades se ha vuelto casi un “MUST” de nuestras aplicaciones. Para utilizarlas, primero debemos asegurarnos de que utilizamos la librería

...
compile 'com.android.support:appcompat-v7:23.3.0'
...

Para comenzar, deberemos indicar en el nuestro tema de aplicación en values-v21 que se van a realizar animaciones y de que tipo son:

...



<style name="AppTheme" parent="AppTheme.Base">
    <item name="android:windowContentTransitions">true</item>
    <item name="android:windowAllowEnterTransitionOverlap">true</item>
    <item name="android:windowAllowReturnTransitionOverlap">true</item>
    <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
    <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
</style>



...

Debemos definir y ver que elementos queremos compartir y que actividades están relacionadas. Una vez localizados, comenzaremos por los ficheros de layouts, incluyendo en los elementos que queramos compartir(tanto en el origen como en el destino), el nombre con el que vamos a identificar a la transición de la vista en cuestión. En el nuestro caso, vamos a compartir un ImageView.

...
<ImageView android:id="@+id/iv_user_avatar" android:transitionName="@string/transition_avatar" android:layout_width="60dp" android:layout_height="60dp" android:layout_gravity="center"/>
...

Una vez hecho esto, deberemos indicar en la actividad de origen que elemento queremos compartir y con que nombre. Para ello deberemos utilizar clase ActivityOptionsCompat, la cual nos facilitará la tarea. En esta clase, deberemos indicar qué vista es y con que nombre de transición y pasárselo al intent de inicio de actividad.

Intent intent = new Intent(this, UserDetailActivity.class);

intent.putExtra(UserDetailActivity.EXTRA_USER, user);

ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(mImageView, getString(R.string.transition_avatar) ) ;

startActivity(intent, options.toBundle());

Si queremos compartir más de un elemento, deberemos usar tantos elementos Pair<View, String> como sea necesario.

Intent intent = new Intent(this, UserDetailActivity.class);

intent.putExtra(UserDetailActivity.EXTRA_USER, user);

Pair<View, String> p1 = Pair.create((View) holder.getIvUserAvarar(), getString(R.string.transition_avatar));

Pair<View, String> p2 = Pair.create((View) holder.getTvUserName(), getString(R.string.transition_name));

Pair<View, String> p3 = Pair.create((View) holder.getTvUserSurname(), getString(R.string.transition_surname));

Pair<View, String> p4 = Pair.create((View) holder.getTvUserActor(), getString(R.string.transition_actor));

ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, p1, p2, p3, p4);

startActivity(intent, options.toBundle());

Para finalizar, en la actividad de origen sólo debemos inicializar el valor de la vista con lo que queramos. En nuestro caso, es un recurso de la aplicación.

 

Transiciones entre Fragmentos

Muchos de los proyectos, en vez de hacerse todo entre actividades, se utilizan fragments con solo una actividad. Como es normal, también existen las transiciones entre fragments.
Lo primero que debemos saber, es que en este caso si que debemos preocuparnos de la versión en la que se está ejecutando la aplicación. Para realizar las transiciones, deberemos tener la referencia de ambos fragmentos, indicarle la que queremos que se produzca, e insertar todo en el FragmentTransaction

...
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

heroeDetailFragment = HeroeDetailFragment.newInstance(heroe);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    // Inflate transitions to apply
    Transition changeTransform = TransitionInflater.from(this).inflateTransition(R.transition.change_image_transform);

    Transition explodeTransform = TransitionInflater.from(this).inflateTransition(android.R.transition.explode);

    // Setup exit transition on first fragment
    heroesListFragment.setSharedElementReturnTransition(changeTransform);
    heroesListFragment.setExitTransition(explodeTransform);

    // Setup enter transition on second fragment
    heroeDetailFragment.setSharedElementEnterTransition(changeTransform);
    heroeDetailFragment.setEnterTransition(explodeTransform);

    transaction.addSharedElement(holder.getIvAvatar(), getString(R.string.

}

transaction.addToBackStack(null);

transaction.replace(R.id.main_container, heroeDetailFragment );

transaction.commit();
...

Para acabar, puedes ver el código  en un proyecto de ejemplo de GITHUB

SOLID

Principios S.O.L.I.D

Una de las palabras más oídas en la CODEMOTION 2015 fue “S.O.L.I.D” y no es para menos, ya que con el se definen unas buenas prácticas. En un principio pensé que era algo novedoso, algo que habían creado para el desarrollo mobile, pero ni mucho menos es así.
Este conjunto de principios se remonta a principios de la década de los 2000, cuando Robert C. Martin, también conocido como “Uncle Bob”, creó esta regla mnemotécnica de principios básicos para el desarrollo orientado a objetos. La aplicación de estos principios hace que el código creado sea mucho más mantenible y expandible, siendo menor la creación de código spaghetti, y las refactorizaciones sobre el mismo.

Cada una de las letras corresponde con un principio diferente:


Single Responsibility Principle

A class should have one, and only one, reason to change.
(Una clase debería tener una y solo una, razón para cambiar)

Cada clase es responsable de solo una tarea, por lo que si es modificada, solo debería ser porque esa tarea ha sido cambiada. Si en algún momento nuestra clase tiene más de una responsabilidad, éstas deben desacoplarse en distintas clases.


Open Closed Principle

You should be able to extend a classes behavior, without modifying it.
(Deberías ser capaz de extender el comportamiento de una clase sin modificarlo)

Una clase debe estar abierta a extensión pero no a su modificación. Esto se consigue mediante herencia. Teniendo una clase padre que tiene un comportamiento específico, puedes extender esta clase a una clase hija, pudiendo esta tener más funcionalidad, pero manteniendo la anterior. Con esto consigues que la clase padre esté cerrada a modificación pero abierta a la extensión.


Liskov Substitution Principle

Derived classes must be substitutable for their base classes.
(Las clases derivadas deben ser sustituibles por sus clases base)

Quizás sea el punto en el que más dudas tiene la gente. En este principio se comenta una clase que extiende de otra puede usarse como sustituta de su padre sin que su comportamiento se modifique.Pongamos un ejemplo para que quede más claro. Tenemos una clase “Telefono” que contiene un método que es “realizarLlamada()”. Si tuviéramos una clase “SmartPhone” que extiende de “Telefono”, podríamos sustituir todas las referencias a “Telefono” por referencias a “SmartPhone”


Interface Segregation Principle

Make fine grained interfaces that are client specific.
(Haz interfaces pequeñas que sean para un cliente específico)

Este principio nos indica que se usen interfaces para mostrar a los clientes que las usen sólo los métodos que necesiten, así evitaremos que tengan acceso a ciertas partes que no deben ver.
En el desarrollo android, en especial en MVP, tenemos un caso clarísimo. Normalmente las actividades o fragmentos implementan la parte de las vistas, pasándole a los presentadores nuestra implementación. El presentador solo tendrá acceso a los métodos de la interfaz, ocultándole así los métodos del fragment/actividad (onCreate, onResume, etc..) que no necesitan.


Dependency Inversion Principle

Depend on abstractions, not on concretions.
(Haz que dependas de abstracciones y no de concreciones)

Por último, el principio de inversión de dependencias nos indica que se debe depender de abstracciones y no de concreciones de clases. Si tenemos una clase que implementa una interfaz deberíamos poder ser capaces de usar la interfaz como ejecutora de los métodos sin que eso suponga un problema.
La inyección de dependencias en android mediante Dagger / Dagger2 , nos ayuda a cumplir este principio.

keep_calm_solid

Para finalizar, comentar que estos principios no son obligatorios ni la única verdad existente; Son buenas prácticas, que si las sigues, tu código, tu trabajo y sobre todo TUS COMPAÑEROS te lo agradecerán.

Puedes ver AQUI el artículo de Uncle Bob en el que se exponen estos principios.

realm_header

Almacenamiento en Android: Realm como base de datos

Introducción

Como casi todos sabréis, el almacenamiento en android puede dividirse en 3 grandes grupos: preferencias, ficheros y bases de datos. Estas ultimas son las que mas se utilizan en el desarrollo de aplicaciones para persistir datos, en especial SQLite. Gracias a distintos ORM’s (OrmLite, GreenDao, SQLBrite,..), el desarrollo en este tipo de base de datos se ha simplificado.
Durante los últimos tiempos, se ha popularizado el uso de un nuevo tipo de base de datos, el cual no es un ORM de SQLite, sino un gestor propio: REALM

¿Por qué debo usar Realm?

  • Realm es una base de datos muy fácil de usar. En cuestión de minutos puedes tener tu aplicación funcionando con la base de datos. Realm no es un ORM ni un wrapper sobre SQLite. En vez de esto, contiene su propio motor de persistencia
  • Realm es muy rápido.Cuando digo rápido es realmente rápido. En un segundo puede insertar 20.000 registros, por los 9.000 de SQLite, 5.000 de ORMLite, y los apenas 2.000 de GreenDao. Las queries y el conteo de registros, mas veloz aun, sacándole mas ventaja a sus seguidores.

realm_android_insertsrealm_android_queries

  • realm_android_countsRealm es multi-plataforma, por lo que si en vuestro proyecto estáis desarrollando tanto versiones de android como de iOS, podéis optar por este mismo gestor de base de datos. Para iOS, está desarrollado para swift y para objective-c.
  • Realm permite un uso avanzado de la base de datos, pudiendo cifrarse fácilmente, además de poder modificar las políticas de migración de versiones.

Configurar Realm en nuestro proyecto Android

La versión que vamos a utilizar es la 0.88.3. Para utilizar este gestor de base de datos, debemos asegurarnos de que cumplimos los requisitos mínimos. En esta versión de Realm, se necesita:

  • Versión de Android Studio sea igual o mayor que la 1.5.1,
  • Versión de JDK mayor o igual que 7.
  • La versión de la aplicación (API Level)  tiene que ser mayor que Gingerbread, (versión 9, 2.3)

Primero, debemos configurar nuestro proyecto. Actualmente, Realm se instala como un plugin de gradle. Se configura en 2 pasos:

  1. Debemos añadir, en el build.gradle a nivel de proyecto, de donde recoger el plugin. Para ello necesitamos indicar el classpath de Realm, quedando este fichero de esta manera.
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
        classpath "io.realm:realm-gradle-plugin:0.88.3"
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

2. Aplicaremos el plugin de Realm en tantos módulos del proyecto como necesitemos o queramos. Deberemos incluir apply plugin: ‘realm-android en los build.gradle de los módulos que queramos

apply plugin: 'com.android.application'
apply plugin: 'realm-android'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.asy.realmexample"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.2.1'
}

 

Inicializando Realm

Lo primero que debemos hacer para usar Realm, es configurar la base de datos. Podemos utilizar una configuración básica, pero desde mi punto de vista, esto no lo recomiendo, ya que es mejor preparar la base de datos tal y como la queramos. Podemos indicarle el nombre a nuestra base de datos, indicar la política de migración de datos, el version del esquema, encriptación y otras muchas mas configuraciones que se pueden añadir.

...

RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(application)
                .name("cars.realm")
                .deleteRealmIfMigrationNeeded()
                .schemaVersion(1)
                .build();

        Realm.setDefaultConfiguration(realmConfiguration);

...

 

Creando el modelo de datos

Para crear el modelo de datos en Realm, solo necesitaremos extender de la clase “RealmObject”. Deberemos asignarle una clave primaria con la anotación @PrimaryKey.

**
 * Database model for the entity CAR.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 13/4/16.
 */
public class CarDb extends RealmObject {

    public static final String K_CAR_PLATE_NUMBER = "plateNumber";

    @PrimaryKey
    private String plateNumber;

    private Date registrationDate;

    private String model;

    private String brand;

    public CarDb() {
    }

    private CarDb(Builder builder) {

        this.registrationDate = builder.registrationDate;

        this.model = builder.model;

        this.plateNumber = builder.plateNumber;

        this.brand = builder.brand;

    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getPlateNumber() {
        return plateNumber;
    }

    public void setPlateNumber(String plateNumber) {
        this.plateNumber = plateNumber;
    }

    public Date getRegistrationDate() {
        return registrationDate;
    }

    public void setRegistrationDate(Date registrationDate) {
        this.registrationDate = registrationDate;
    }

    public static class Builder {

        private Date registrationDate;

        private String model;

        private String plateNumber;

        private String brand;

        public Builder setBrand(String brand) {
            this.brand = brand;
            return this;
        }

        public Builder setModel(String model) {
            this.model = model;
            return this;
        }

        public Builder setPlateNumber(String plateNumber) {
            this.plateNumber = plateNumber;
            return this;
        }

        public Builder setRegistrationDate(Date registrationDate) {
            this.registrationDate = registrationDate;
            return this;
        }

        public CarDb build() {
            return new CarDb(this);
        }
    }

}

 

Insertar datos.

Existen varias formas para realizar las inserciones de datos:

  • Creando la instancia por el constructor.
...

//Request realm instance
Realm realm = Realm.getDefaultInstance();

//Init the element
CarDb car = new CarDb();

car.setPlateNumber("1111-SSS");

car.setBrand("Citroen");

car.setModel("ZX");


//Insert element
realm.beginTransaction();

realm.copyToRealm(carDb);

realm.commitTransaction();

...
  • Obteniendo un objeto desde Realm.
...

//Request realm instance
Realm realm = Realm.getDefaultInstance();

//Begin transaction
realm.beginTransaction();

//Init the element
CarDb car = realm.createObject(CarDb.class);

car.setPlateNumber("1111-SSS");

car.setBrand("Citroen");

car.setModel("ZX");

//Commit transaction
realm.commitTransaction();

...

Realizar consultas de datos.

Se pueden realizar consultas de todo tipo: Filtrados por fechas, campos mayores,menores, o iguales que ciertos valores,..

...

Realm realm = Realm.getDefaultInstance();

RealmResults<CarDb> results = realm.where(CarDb.class)
                .equalTo(CarDb.K_CAR_PLATE_NUMBER, "1111-SSS")
                .or()
                .equalTo(CarDb.K_CAR_PLATE_NUMBER, "3333-XXX")
                .findAll();

...

Eliminar elementos.

Para suprimir objetos de nuestra base de datos, primero deberemos realizar una consulta de los objetos que queremos eliminar. A continuación, realizaremos el limpiado de los resultados.

...

Realm realm = Realm.getDefaultInstance();

realm.beginTransaction();

//Make the query.
RealmQuery<CarDb> realmQuery = realm.where(CarDb.class).equalTo(CarDb.K_CAR_PLATE_NUMBER, "1111-SSS");

RealmResults<CarDb> realmResults = realmQuery.findAll();

//Remove elements.
realmResults.clear();

//Commit changes.
realm.commitTransaction();

...

 

Problemas que me he encontrado usando Realm.

  1. Por ahora, no existe un campo autoincrementable, muy util para id’s de atributos autogenerados.
  2. No se puede extender de una clase que a su vez extienda de RealmObject.
  3. Al principio tuve problemas ejecutando las consultas en un hilo secundario. Esto se soluciona teniendo una clase que sea proveedora.

AQUI puedes ver un ejemplo de Realm, con MVP y arquitectura clean (basado en el ejemplo de Jorge Castillo).

app_launch_checker

Android Support Library (I) – AppLaunchChecker

A principios de abril, el equipo de desarrollo de Google lanzó la nueva versión de su librería de soporte, la versión 23.3.0 . En esta versión se han solucionado una gran cantidad de bugs. Podéis ver los cambios AQUÍ.

Esta entrada no es para comentar los bugs, sino para hablar de un nuevo elemento que han implementado. AppLaunchChecker

¿Qué es AppLaunchChecker?

Esta clase ofrece la posibilidad de saber si la aplicación se ha ejecutado alguna vez. Para ello solo se necesita inicializar la clase en el método onCreate(Bundle savedInstanceState) de nuestra actividad principal, y preguntar, donde queramos, si la aplicación ha sido lanzada alguna vez.

¿Cómo funciona realmente AppLaunchChecker?

AppLaunchChecker es un simple gestor de shared preferences, el cual ofrece dos métodos públicos. El primero es onActivityCreate(Activity activity) , que es el inicializa la clase. Lo primero que realiza es comprobar el valor actual de la preferencia, y si ya está inicializada no sigue. Por el contrario, si la variable no tiene valor, pondrá la preferencia a true si la actividad es ACTION_MAIN y además tiene alguno de los flags CATEGORY_LAUNCHER o CATEGORY_LEANBACK_LAUNCHER.

El método hasStartedFromLauncher(Context context) lo único que realiza es devolver el valor de la preferencia.

Una vez se ejecute por primera ver el onActivityCreate(Activity activity), el métodohasStartedFromLauncher(Context context) siempre nos devolverá true, por lo que si queremos ver si es la primera vez que se ejecuta, deberemos ejecutar las llamadas a los métodos en el orden correcto.

¿Para que me sirve en mis aplicaciones?

Muchos desarrolladores de aplicaciones necesitan al inicio de la aplicación recuperar algunos datos de un servidor, o inicializar ciertos aspectos (ya sea configuraciones personalizadas, como propias,..). Antes se hacia la misma comprobación (Si no es la misma, muy parecida). Se creaba una preferencia que indicaba si ya se había inicializado la aplicación.

 

Ejemplo práctico

public class MainActivity extends AppCompatActivity {

    private TextView tvAppState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        tvAppState = (TextView) findViewById( R.id.tv_app_state );

        if( AppLaunchChecker.hasStartedFromLauncher(this) ){

            tvAppState.setText("Application has been launched at least once");

        }else{

            tvAppState.setText("Application has never been started");

        }
        AppLaunchChecker.onActivityCreate( this );

    }
    
}

 

Puedes ver el ejemplo AQUI

patron_I

Patrones de diseño en Android (I): Patrones de creación

Patrones de diseño en Android (I) : Patrones de creación
Patrones de diseño en Android (II): Patrones estructurales

El desarrollo de software no es una ciencia cierta. Siempre tenemos que realizar tareas diferentes, pero dentro de estas tareas, muchas veces nos encontramos con un mismo problema recurrente.

 

Cada patrón describe un problema que ocurre una y otra vez en nuestro entorno, así como la solución a ese problema, de tal modo que se pueda aplicar esta solución un millón de veces, sin hacer lo mismo dos veces.

 

Bajo esta premisa, realizaron los patrones de diseño. En el libro “Patrones de diseño” de Erich Gamma se dice que “…un patrón de diseño nomina, abstrae, e identifica los aspectos clave de una estructura de diseño común,lo que los hace útiles para crear un diseño orientado a objetos reutilizable..”

 

Los patrones de diseño en el ámbito del desarrollo software se dividen en tres categorías diferentes.

  • Patrones de creación, encargados de abstraer el proceso de creación de nuevas instancias de nuestra clase.
  • Patrones estructurales, que se ocupan de como combinar clases y objetos para formar estructuras mas grandes.
  • Patrones de comportamiento, los cuales no solo se encargan de describir patrones de clases y objetos, sino también la comunicación entre ambos.

En esta primera entrada nos vamos a centrar en los patrones de creación, en especial en los patrones “Builder”, “Singleton”, y “Factory Method”

Patrón Builder

Patrón utilizado para crear instancias de elementos complejas. Con este patrón se tiene un proceso de creación mas fácil e intuitivo para el desarrollador. Es un patrón muy utilizado dentro del desarrollo android, ya que permite crear nuevas instancias muy personalizadas. Un ejemplo muy claro es la clase “AlertDialog”, la cual se puede instanciar mediante este tipo de patrón.

/**
 * Entity that represents a "Car".
 *
 * @author asanchezyu@gmail.com
 * @version 1.0.
 * @since 8/4/16.
 */
public class Car {

    private final String plateNumber;
    private final String ownerDrivingLicenseId;
    private final String colour;
    private final String type;
    private final String year;

    private Car(Builder builder) {
        this.plateNumber = builder.plateNumber;
        this.ownerDrivingLicenseId = builder.ownerDrivingLicenseId;
        this.colour = builder.colour;
        this.type = builder.type;
        this.year = builder.year;
    }

    public String getColour() {
        return colour;
    }

    public String getOwnerDrivingLicenseId() {
        return ownerDrivingLicenseId;
    }

    public String getPlateNumber() {
        return plateNumber;
    }

    public String getType() {
        return type;
    }

    public String getYear() {
        return year;
    }

    /**
     * Builder class.
     */
    public static class Builder {

        private final String plateNumber;
        private final String ownerDrivingLicenseId;
        private String colour;
        private String type;
        private String year;

        public Builder(String plateNumber, String ownerDrivingLicenseId) {
            this.plateNumber = plateNumber;
            this.ownerDrivingLicenseId = ownerDrivingLicenseId;
        }

        public Builder setColour(String colour) {
            this.colour = colour;
            return this;
        }

        public Builder setType(String type) {
            this.type = type;
            return this;
        }

        public Builder setYear(String year) {
            this.year = year;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }
}
Builder

Patrón Singleton

Este patrón nos permite tener una sola instancia de una clase en toda nuestra aplicación. Este patrón es muy útil cuando utilizamos managers de bases de datos, de peticiones REST, cola de tareas en background, ya que solo queremos que se instancie una sola vez. Si se trabaja sobre la instancia en diferentes hilos, debemos tener control sobre la concurrencia, tal y como se ve en el siguiente ejemplo.

/**
 * Database manager.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public class DbManager {

    private static volatile DbManager instance = null;

    private DbManager() {
        // No instances created from outside.
    }

    public static DbManager getInstance() {
        if (instance == null) {
            synchronized (DbManager.class) {
                if (instance == null) {
                    instance = new DbManager();
                }
            }
        }
        return instance;
    }

}
Singleton

 

Patrón Factory Method

Este patrón de creación nos permite desde una clase abstracta constructora crear instancias de otros objetos que a su vez, heredan o implementan un elemento en común . Este patrón es muy útil cuando desde nuestra aplicación, por ejemplo, tenemos varios entornos de desarrollo (desarrollo, pre-producción y producción), y cada uno de ellos tiene un endPoint diferente. Gracia a este patrón, el cambiar de entorno se simplifica en un cambio de variable.

Pongamos el anterior ejemplo en práctica:

Nuestra aplicación tendrá dos entornos (“environments”). Ambos entornos nos tendrán que devolver la url de nuestro blog. Para ello creamos una intefaz “Environment” y dos clases que la implementan, “DevEnvironment” y “ProductionEnvironment”.

/**
 * Interface that defines the methods of one environment.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public interface Environment {

    String getBlogUrl();

}
Environment
/**
 * Class that represents one type of enviroment: Development Environment.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public class DevEnvironment implements Environment {

    @Override
    public String getBlogUrl() {

        return "http://localhost:8080/myBlog";

    }
}
DevEnvironment
/**
 * Class that represents one type of enviroment: Production Environment.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public class ProductionEnvironment implements Environment {

    @Override
    public String getBlogUrl() {

        return "http://blog.asanchez-portfolio.es";

    }
}
ProductionEnvironment

Para crear instancias de estos elementos, crearemos una factoría abstracta “EnvironmentFactory” y que a su vez será implementada por la factoría de nuestra aplicación “ApplicationEnvironmentFactory”.

/**
 * Interface that defines needed methods for one environment factory.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public interface EnvironmentFactory {

    enum EnvironmentType {
        DEVELOPMENT,
        PRODUCTION
    }

    Environment getEnvironment(EnvironmentType environmentType);

}
EnvironmentFactory
/**
 * Class that represents one type of environment creator: Our application environment factory.
 *
 * @author asanchezyu@gmail.com.
 * @version 1.0.
 * @since 8/4/16.
 */
public class ApplicationEnvironmentFactory implements EnvironmentFactory {

    @Override
    public Environment getEnvironment(EnvironmentType environmentType) {
        Environment environment;

        switch (environmentType) {
            case DEVELOPMENT:
                environment = new DevEnvironment();
                break;
            case PRODUCTION:
                environment = new ProductionEnvironment();
                break;
            default:
                environment = new DevEnvironment();
                break;
        }

        return environment;
    }
}
ApplicationEnvironmentFactory

Una vez hecho esto, desde la clase que queramos utilizar la factoría, solo debemos realizar lo siguiente:

...

EnvironmentFactory environmentFactory = new ApplicationEnvironmentFactory();

Environment debugEnvironment = environmentFactory.getEnvironment(EnvironmentFactory.EnvironmentType.DEVELOPMENT);

Environment productionEnvironment = environmentFactory.getEnvironment(EnvironmentFactory.EnvironmentType.PRODUCTION);

System.out.println("Blog url from debug  " + debugEnvironment.getBlogUrl());

System.out.println("Blog url from production  " + productionEnvironment.getBlogUrl());

...
How to use

Puedes ver el código en github