Organizando dependencias con Gradle

Buenas a todos!

En un proyecto Android, Gradle es el encargado de las tareas de compilación, empaquetado y generación de apks, entre otras tareas. Hoy quiero hablar sobre una de estas tareas: la gestión dependencias de terceros.

Gradle permite añadir dependencias a nuestro proyecto de una forma tan fácil como añadir una línea al fichero build.gradle del módulo app. Algo parecido a esto.

Esto tiene un problema cuando la biblioteca en cuestión está dividida en varias dependencias, y tienes que cambiar la versión de cada una de ellas. Si se declaran así las dependencias tendremos que actualizar la versión tantas veces como la uses en cada uno de los módulos de tu aplicación.

Para agilizar la actualización de versiones y para ordenar y limpiar los ficheros gradle de mi aplicación investigué como podía definir la versión de la biblioteca aparte en un solo lugar y así cuando haya que actualizar la versión, cambiarlo sólo ahí. Encontré en el repo Android-CleanArchitecture de Fernando Cejas una forma que me gustó.

Las dependencias de todo el proyecto se definen en un nuevo fichero, dependencies.gradle. En él se definen las versiones de cada biblioteca y además el paquete asociado a la biblioteca. Este fichero tiene esta pinta:

Las depedencias del módulo app quedan muy limpias, ya que además de llevarnos las versiones a otro fichero, también movimos el nombre del paquete, el SDK mínimo, el código de versión y el nombre de la versión.

Ahora ya no tenemos cambiar las versiones en muchos sitios, sino solo en uno. Otra ventaja que me parece interesante es que este fichero de dependencias aparte podría ser compartido para el resto de apps que desarrolles. Es decir, normalmente las dependencias entre las aplicaciones no suelen variar, por lo que podría copiarse cambiando el nombre del paquete y la versión y todo seguiría funcionando 🙂

Un saludo!

Inyección de dependencias en Swift

Buenas a todos!

Como ya he comentado en algún otro post, últimamente he estado migrando mi aplicación TopProfe a Kotlin. La aplicación migrada al 90% ya se encuentra en el PlayStore  😁

Una vez conseguido ese primer reto, comencé a desarrollar la misma aplicación pero para dispositivos iOS en Swift. Iré comentando por aquí las principales diferencias que vaya encontrando tanto a nivel de lenguaje como de plataforma de desarrollo.

En este primer post sobre Swift, voy a explicar como resolví el problema de la inyección de dependencias sin usar ningún framework para ello. En la aplicación de TopProfe utilizo Dagger 2 para resolver este problema, por lo que comencé a buscar frameworks parecidos para Swift.

Encontré alguno que otro como por ejemplo Cleanse de Square ó Swinject, pero ninguno me convenció. La curva de aprendizaje era demasiado grande como para un proyecto que quiero sacar rápido y probar.

Así que comencé a buscar posts sobre como los desarrolladores de iOS resuelven este problema, y me recomendaron este de @juan_cazalla en el que planteaba una solución. Me encantó la idea de los protocolos encargados de definir las dependencias, y sus extensiones de instanciarlas.

Las extensiones no me solucionaban el problema al 100% porque no permiten contener instancias, por lo que no podía tener mis instancias globales únicas. Así que decidí adaptarlo a mi problema añadiendo un nuevo módulo que se encarga de proveer las dependencias globales comunes a toda la aplicación.

Quiero enseñaros mi solución a partir del post de Juan. En mi caso necesitaba tener instancias globales únicas, como por ejemplo las instancias referidas a los repositorios de datos, por lo que decidí separar mis dependencias en módulos. Cada VC tiene definido su módulo asociado con las dependencias que necesita, de esta forma:

Por tanto, el módulo que se expone al exterior solo conoce como proveer el VC ya que las dependencias asociadas con este se definen en el protocolo ProfessorsModuleAssembler que es privado.

Todos los módulos reciben por constructor una instancia de CommonAssembler, que es el encargado de proveer las instancias globales anteriormente mencionadas. Tiene esta pinta más o menos:

Con instancias lazy de los repositorios me aseguro de que solo se instancian la primera vez que se quiere hacer uso, evitando instanciar todo cuando se inicie la aplicación. Aparte, consigo que sea la misma instancia siempre la que se pasa a los distintos módulos que proveen las dependencias lo que me permite escribir fácilmente los test.

Os estaréis preguntando cual es la forma de crear los módulos para cada VC, y es la clase DependencyFactory la encargada también de esto.

DependencyFactory implementa el protocol público ProfessorsModule y además tiene una instancia lazy de este mismo, que actúa como delegado. No me termina de convencer del todo esta aproximación, ya que de momento solo hay un VC pero cuando la aplicación crezca, esta clase podría llegar a ser enorme. En cuanto tenga una solución mejor, la compartiré 🤓

Espero que os haya gustado!

Kotlin interfaces mutantes

Buenas!

Hace ya casi un año que publiqué mi aplicación TopProfe para votar a los profesores de mi universidad y últimamente he decidido que es un buen momento para refactorizarlo y además, migrarlo a Kotlin.

Tras leerme el libro de @lime_cl, decidí ponerme manos a la obra y migrar la aplicación entera a Kotlin. Poco a poco iré escribiendo posts sobre las cosas chulas que he cambiado, y como ha mejorado el código con la introducción de Kotlin.

La primera feature de la que os quiero hablar es la posibilidad que Kotlin brinda para poder implementar métodos en las interfaces. También se pueden definir variables que más tarde sobreescribirá el que implemente dicha interfaz.

Imaginad la aplicación de TopProfe, que tiene varios listados de profesores, la interfaz que implementa la vista sería algo así en Java:

Los típicos métodos para añadir el listado de profesores al adapter correspondiente, y para navegar hasta el detalle de un profesor al hacer click sobre él. Todos los Fragments/Activities que implementen esta interfaz tendrán código repetido que es costoso mantener.

La solución que he adoptado en Kotlin para no repetir código, consiste en usar lo que he bautizado como ‘interfaces mutantes’ introduciendo en ellas código compartido entre todos los listados de profesores.

De esta forma, las vistas que tengan un listado de profesores tan solo tendrán que sobreescribir las dos variables de la interfaz, el Adapter y el Context necesario, y no tendrán código repetido. El adapter es uno genérico que me permite no tener que escribir un adapter para cada tipo de datos que quiera mostrar. En el siguiente post hablaré sobre cómo está implementado :).

En el caso de los listados de las asignaturas, la interfaz mutante queda de la siguiente manera. Si os dais cuenta, se vuelve a repetir código, y si el objetivo de esto es no hacerlo… ¿estoy ganando algo?

Cuando me di cuenta de que no era la solución más óptima, decidí ir un poco más allá. Como el Adapter está preparado para recibir genéricos, podría separar aún más las operaciones que hay que hacer para añadir elementos al Adapter. La solución es esta:

Así el código para añadir elementos al Adapter se escribe una sola vez, y la interfaces anteriores quedan de esta manera:

De momento este es el primer post de mis experiencias con Kotlin, pero pronto iré publicando más cositas :)!

Nos vemos!

Git aliases

¡Buenas!

Actualmente en el desarrollo de software es indispensable usar un software de control de versiones. El más utilizado es Git, pero existen otros muchos como VCS ó Subversion.

Hay muchos clientes de Git que proveen una interfaz gráfica para mejorar la visión de la gestión de ramas dentro del proyecto. Facilitan el seguimiento de una rama, su origen, su estado y su muerte, pero, después de haber probado SourceTree y GitKraken, aunque este último no tan a fondo, sigo prefiriendo la consola.

Mediante línea de comandos me siento más cómodo, y me parece que controlo más lo que necesito en cada gestión de Git. Creo que el potencial de esta herramienta se pierde un poco al utilizarlo desde uno de estos clientes.

Hoy me gustaría enseñaros un alias que he añadido a mis herramientas de Git para poder facilitarme el trabajo diario en Idealista. El primer paso es que cada tarea se identifique con un número que es único, y que por cada tarea exista una rama cuyo nombre contenga el identificador de la tarea a la que pertenece. Esto facilita el siguimiento de las tareas, y la identificación de las ramas en las que se resuelven.

La siguiente línea se añade en el fichero .gitconfig situado en la home de vuestro equipo.

[alias]
task = "!sh -c \"git branch | grep $1 | xargs git checkout\""

Gracias a esta línea podemos movernos entre ramas solo conociendo el identificador de la tarea(ó una palabra que contenga el nombre de la rama). Por ejemplo, si quiero moverme a la rama de la tarea 3100, lo puedo hacer en un simple comando y sin conocer el nombre completo de la rama porque no me interesa. Me podría mover a dicha rama simplemente ejecutando lo siguiente:

git task 3100

Es interesante seguir mejorando los conocimientos de Git e intentar ser más eficientes cada día 🙂

Un saludo!

¡Soy español en Android!

Hola a todos!

Como os comenté en el post anterior, en septiembre empecé un Máster de Ingeniería Informática por la UNED. Cuando ví las asignaturas, se me iluminaron los ojos al leer ‘Sistemas operativos de dispositivos móviles’. Mi gozo en un pozo al ver que era optativa, pero bueno, oye, que la iba a cursar estaba seguro.

La asignatura tiene nueve temas, en los que cinco de ellos son de Android y el resto de iOS. El temario sobre ambos sistemas operativos está muy bien estructurado, y explicado, entra en detalles que como desarrollador de aplicaciones no conoces.

En el caso de Android explica en detalle cada uno de los ficheros necesarios para el boot y los pasos necesarios en el arranque. Entendí por fin quién o qué es zygote, y como funciona. Son transparencias realmente recomendables.

La primera práctica de la asignatura fue arrancar un emulador y meterse en la shell del dispositivo. Una vez ahí podías ver los ficheros de los que se hablaba en la teoría, y como con todo en la vida, es interesante ver que la teoría se puede aplicar a la práctica.

La segunda práctica consiste en hacer una aplicación. Esta aplicación consta de un cuadro de texto en el que el usuario introduce su nombre, y seguidamente, aparece en pantalla un saludo personalizado con su nombre. A medida que avanzas la práctica se complica. Se van añadiendo funcionalidades como guardar el nombre del usuario en disco para cuando arranque la aplicación, aparezca el saludo ya sin que se vuelva a introducir su nombre.

Una práctica introductoria, pero interesante que te explica lo que es el framework de Android y como funciona. También te familiarizas con el uso de Android Studio y el emulador si no lo habías usado antes.

Hasta aquí, todo perfecto e interesante. Pero me gustaría enseñaros el código final de la práctica siguiendo las indicaciones de la memoria.

public class ActividadPrincipal extends AppCompatActivity {

    public final static String SALUDO = "com.uned.dia.saludar.SALUDO";
    public final static String NOMBRE_GUARDADO_EN_DISCO = "com.uned.dia.saludar.NOMBRE_GUARDADO_EN_DISCO";
    public final static String SALUDO_GUARDADO = "com.uned.dia.saludar.SALUDO_GUARDADO";
    public static final String DESPEDIDA = "com.uned.dia.saludar.DESPEDIDA";

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

        SharedPreferences preferencias = this.getPreferences(Context.MODE_PRIVATE);
        String nombreGuardado = preferencias.getString(NOMBRE_GUARDADO_EN_DISCO, "");
        escribirNombre(nombreGuardado);
    }

    public void Despedida(View view) {
        String textoDespedida = crearDespedida(obtenerNombre());
        escribirSalida("");
        Intent intent = new Intent(this, Despedida.class);
        intent.putExtra(DESPEDIDA, textoDespedida);
        startActivity(intent);
    }

    private String crearDespedida(String s) {
        return String.format(Locale.getDefault(), getString(R.string.despedida), s);
    }

    public void GuardarNombre(View view) {
        String nombre = obtenerNombre();
        SharedPreferences preferencias = this.getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferencias.edit();
        editor.putString(NOMBRE_GUARDADO_EN_DISCO, nombre);
        editor.commit();
        escribirNombre(getString(R.string.nombre_guardado_OK));
    }

    public void SaludarOtraApp(View view) {
        String textoSaludo = crearSaludo(obtenerNombre());
        escribirSalida("");
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SEND);
        intent.putExtra(Intent.EXTRA_TEXT, textoSaludo);
        intent.setType("text/plain");
        PackageManager packageManager = getPackageManager();
        List activities= packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        if(activities.size() > 0){ intent.putExtra(Intent.EXTRA_TEXT, textoSaludo);
            startActivity(intent);
        }
        else {
            escribirSalida( getString(R.string.error_lanzando_app));
        }
    }

    public void SaludarActividadNueva(View view) {
        String textoSaludo = crearSaludo(obtenerNombre());
        escribirSalida("");
        Intent intent = new Intent(this, MostrarSaludo.class);
        intent.putExtra(SALUDO, textoSaludo);
        startActivity(intent);
    }

    public String obtenerNombre() {
        EditText editarTexto = (EditText) findViewById(R.id.entradaNombre);
        return editarTexto.getText().toString();
    }

    public void escribirNombre(String nombre) {
        EditText editarTexto = (EditText) findViewById(R.id.entradaNombre);
        editarTexto.setText(nombre);
    }

    public String crearSaludo(String nombre) {
        String saludo = obtenerSaludoAleatorio();
        return String.format(Locale.getDefault(), saludo, nombre);
    }

    private String obtenerSaludoAleatorio() {
        String saludo;
        Random random = new Random(System.currentTimeMillis());
        int randomNum = random.nextInt(10 + 1);
        switch (randomNum) {
            case 0:
                saludo = getString(R.string.saludo_hola);
                break;
            case 1:
                saludo = getString(R.string.saludo_que_tal);
                break;
            case 2:
                saludo = getString(R.string.saludo_buenos_dias);
                break;
            case 3:
                saludo = getString(R.string.saludo_como_estas);
                break;
            case 4:
                saludo = getString(R.string.saludo_ey);
                break;
            case 5:
                saludo = getString(R.string.saludo_good_morning);
                break;
            case 6:
                saludo = getString(R.string.saludo_conocerte);
                break;
            case 7:
                saludo = getString(R.string.saludo_buenas);
                break;
            case 8:
                saludo = getString(R.string.saludo_hola_soy);
                break;
            case 9:
                saludo = getString(R.string.saludo_dia);
                break;
            default:
                saludo = getString(R.string.saludo_soy_android);
        }
        return saludo;
    }

    public String obtenerSalida(){
        TextView respuesta = (TextView) findViewById(R.id.textoSalida);
        return respuesta.getText().toString();
    }

    public void escribirSalida(String textoSalida) {
        TextView respuesta = (TextView) findViewById(R.id.textoSalida);
        respuesta.setText(textoSalida);
    }

    public void metodoSaludar(View view) {
        escribirSalida(crearSaludo(obtenerNombre()));
    }

    @Override
    public void onSaveInstanceState(Bundle datosGuardados) {
        String salida = obtenerSalida();
        datosGuardados.putString(SALUDO_GUARDADO, salida);
        super.onSaveInstanceState(datosGuardados);
    }

    @Override
    public void onRestoreInstanceState(Bundle datosGuardados) {
        super.onRestoreInstanceState(datosGuardados);
        String salida = datosGuardados.getString(SALUDO_GUARDADO);
        escribirSalida(salida);
    }
}

¿Qué os ha parecido? Mi impresión es que a estas alturas, en un máster en el que uno de los requisitos es tener un cierto nivel de inglés, se debería programar en inglés. Otra de las razones podría ser que los métodos del framework si que están en inglés y aún así los entendemos.

Los nombres de los métodos utilizados serían un inglés básico, como por ejemplo, setName o writeName.

Hasta aquí mi crítica a la práctica que he tenido que hacer hoy  🙂

Pd. ¿Qué os parece?

¡Feliz 2017!

¡Feliz año nuevo!

Hace ya algún tiempo que no escribo, y han cambiado varias cosas en mi vida que me gustaría contar. Además, el domingo leyendo la Bolinista me dio una idea estupenda para volver a escribir aquí.

El último post de este blog es de abril, y desde entonces he acabado mi carrera como ‘Teleco’ y he empezado a trabajar como Android Developer en idealista. Durante estos meses he aprendido muchísimas cosas sobre desarrollo en Android que me gustaría empezar a plasmar aquí. Por tanto, mi primer objetivo para el año 2017 es escribir al menos una vez al mes, sé que es fácil de cumplir, pero teniendo en cuenta que llevo nueve meses sin escribir, no me parece mala cifra.

Como os he contado, acabé mi carrera y no he querido quedarme ahí. En septiembre empecé el Máster en Ingeniería Informática por la UNED, y mis ganas de querer sacarlo me hicieron matricularme de diez asignaturas. Sí, una absoluta locura de la que ahora mismo, a menos de un mes de los exámenes me estoy arrepintiendo. Mi segundo objetivo para este año es aprobar cuatro de las seis del primer cuatrimestre, y tres de las cuatro del segundo cuatrimestre. Este objetivo es bastante más ambicioso que el anterior, pero, pase lo que pase, lo habré intentado.

El tercer objetivo es publicar varias aplicaciones, y además, mejorar mi aplicación de TopProfe. La aplicación que empecé a desarrollar con mi tío @j3susalonso y la aplicación de ElMundoToday que estoy desarrollando con mi compañero @vicdefran. Y, porqué no, publicar mi primera aplicación para iOS.

El resto de objetivos son los que cualquier persona por estas fechas se propone, bajar los kilos de las fiestas y alguno más, ir al gimnasio, etc…

El año que viene podré comprobar si los he cumplido, o si he añadido alguno más. ¡Ya veremos!

 

 

 

Backendless BaaS

Buenas a todos,

Después de la retirada de Parse, me decidí por Backendless como mi backend para mis aplicaciones Android. La segunda opción era Firebase, pero finalmente me decidí por Backendless.

En este post quiero explicar todo el procedimiento desde la creación de la cuenta de Backendless, siguiendo por la especificación de los objetos, la descarga de datos siguiendo la API Rest de backendless, hasta la presentación en la aplicación.

El ejemplo que usaremos será sencillo, una simple lista de datos inflada desde nuestro backend. En este caso será una lista con los nombres de nuestros amigos y su fecha de cumpleaños, pero como veremos, cambiar esto por vuestro caso particular es muy sencillo.

1. Preparación de backendless

Lo primero que debemos hacer es crearnos una cuenta en backendless. Una vez logueados en la plataforma, navegaremos hasta la pestaña de ‘Datos’.

El siguiente paso será la creación de la tabla ‘Amigos’ que contendrá tres columnas: nombre, teléfono y fecha de cumpleaños. Para ello, se debe hacer click en el signo ‘+’ situado en el esquina inferior izquierda de la pestaña ‘Datos’.

Una vez creada la tabla ‘Amigos’, crearemos sus columnas. Es conveniente hacer click en el botón rojo ‘Esquema de tablas y permisos’. Añadiremos tres columnas, todas de tipo String. El resultado ha de ser algo parecido a esto:

Captura de pantalla 2016-04-16 a las 19.14.57

Una vez creadas las columnas, se debe rellenar la base de datos. Rellenaremos con datos al azar para este ejemplo. Quedaría algo así:

Captura de pantalla 2016-04-16 a las 19.20.07

2. Android

El proyecto va a constar de una única activity con un recyclerview. Primero vamos a crear el modelo de datos, en nuestro caso Amigo. Es importante que los atributos se llamen iguales que las columnas creadas en backendless. La clase debe tener constructor, además de getters y setters para todos sus atributos.

/**
 * Created by josedelpozo on 16/4/16.
 */
public class Amigo {

    private String nombre;
    private String telefono;
    private String cumple;

    public Amigo(String nombre, String telefono, String cumple) {
        this.nombre = nombre;
        this.telefono = telefono;
        this.cumple = cumple;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getTelefono() {
        return telefono;
    }

    public void setTelefono(String telefono) {
        this.telefono = telefono;
    }

    public String getCumple() {
        return cumple;
    }

    public void setCumple(String cumple) {
        this.cumple = cumple;
    }
}

La respuesta de Backendless es un JSON con atributos de offset, un array de Amigos, un link a la siguiente página y el número de total de objectos que aparecen en el array. Es necesario crear una clase como esta:

/**
* Created by josedelpozo on 17/4/16.
*/
public class BackendlessResponse {

private String offset;
private List<Amigo> data;
private String nextPage;
private float totalObjects;

public BackendlessResponse(String offset, List<Amigo> data, String nextPage, float totalObjects) {
this.offset = offset;
this.data = data;
this.nextPage = nextPage;
this.totalObjects = totalObjects;
}

public String getOffset() {
return offset;
}

public void setOffset(String offset) {
this.offset = offset;
}

public List<Amigo> getData() {
return data;
}

public void setData(List<Amigo> data) {
this.data = data;
}

public String getNextPage() {
return nextPage;
}

public void setNextPage(String nextPage) {
this.nextPage = nextPage;
}

public float getTotalObjects() {
return totalObjects;
}

public void setTotalObjects(float totalObjects) {
this.totalObjects = totalObjects;
}
}

Una vez terminado el modelo, comenzamos con la descarga de datos desde backendless. Como comenté, vamos a usar la API Rest. Para facilitarnos la vida, vamos a usar la librería Retrofit 2.0. Así que, se debe añadir a build.gradle las siguiente líneas:

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'

compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'

El siguiente paso es crear una interfaz, ‘AmigoService’ que define el método a llamar para recuperar nuestros amigos.

/**
 * Created by josedelpozo on 17/4/16.
 */
public interface AmigoService {

    @Headers({"application-id: APP-ID",
            "secret-key: SECRET-KEY",
            "Content-Type: application/json"})
    @GET("/{version}/data/{table-name}")
    Call<BackendlessResponse> getAmigos(@Path("version") String version, @Path("table-name") String table);
}

Vamos a pararnos un momento en este método. Primero se han de definir unas cabeceras que indican una conexión segura hacia nuestra cuenta de backendless. El id de aplicación, y la clave secreta rest se deben copiar y pegar de nuestro dashboard de backendless. Además, definimos que lo que vamos a recibir es un json con los datos de nuestros amigos.

 

Lo siguiente indica que es una petición GET, e indica la dirección a la que haremos dicha petición. En este caso, la versión de nuestra aplicación es v1 y el nombre de la tabla ‘Amigos’.

Este método devuelve un objeto de tipo Call<Object>, siendo object lo que queremos recibir ya parseado. En nuestro caso, BackendlessResponse.

Bien, una vez creado el servicio, hagamos la petición. Como va a ser una aplicación tan sumamente sencilla que solo descargará estos datos, la petición se hará sobre la Activity que mostrará los datos, pero esto no es Clean Code y no se debe hacer así.

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.backendless.com")
.addConverterFactory(GsonConverterFactory.create())
.build();

final AmigoService service = retrofit.create(AmigoService.class);

Call<BackendlessResponse> call = service.getAmigos("v1", "Amigos");
Callback callbackRetrofit = new Callback<BackendlessResponse>() {
@Override
public void onResponse(Call<BackendlessResponse> call, Response<BackendlessResponse> response) {
adapter.addAll(response.body().getData());
}

@Override
public void onFailure(Call<BackendlessResponse> call, Throwable t) {
Log.d("ERROR","Failure "+t.getMessage());
}
};
call.enqueue(callbackRetrofit);

Este código será el encargado de hacer la petición a la dirección https://api.backendless.com que se especifica en el objecto Retrofit. También se especifica que usaremos Gson para parsear de JSON a nuestro objecto Amigo.

 

Seguidamente creamos el servicio, y llamamos al método definido en la interfaz anterior. Realizaremos la petición de forma asíncrona, eso quiere decir que cuando el servidor nos devuelva la respuesta, nos aparecerá en el callback definido. Si no ha habido fallo, esta aparecerá en onResponse, y en caso contrario en onFailure.

Para mostrar estos datos, es necesario montar un recyclerview sobre nuestro layout, y un adapter que muestre esos datos. Este tema no lo voy a comentar aquí, ya que es muy sencillo y hay miles de tutoriales más detallados. El código si que lo dejaré en Github por si alguien quiere consultarlo.

Captura de pantalla 2016-04-17 a las 10.46.29

¡El resultado es este!

GITHUB

Rating Bar

Hola de nuevo!

Estoy haciendo una app para poder votar a los profesores de la universidad, y así, ser por fin nosotros quienes ponen la nota.

Cuando la aplicación estaba casi terminada, en su primera versión, me di cuenta que la RatingBar de Google no se podía dimensionar en tamaño. O al menos, no lo encontré en su guía, ni me funcionaron ninguno de los consejos de stackoverflow.

Captura de pantalla 2016-03-24 a las 11.21.47

Como veis, el tamaño de las estrellas es demasiado grande. Si intento disminuir su height, para que no sean tan grandes, ya que de ancho si quiero que ocupen todo, ocurre lo siguiente:

Captura de pantalla 2016-03-24 a las 11.24.18

Así que, miré varios ejemplos y me decidí a realizar mi propia RatingBar. Os dejo el link a Github, y si alguien quiere ayudar en el proyecto que no dude!

StarsRatingBar

Saludos!

Aprendiendo Kotlin

Hola a todos!

Hace ya un mes que me cree el blog, y aún no me he atrevido a escribir ningún post.

Llevo ya bastante tiempo siguiendo a @lime_cl, y me interesé por Kotlin, el nuevo lenguaje que Antonio lleva tiempo explicando. Como bien os comenté en el primer post explicatorio sobre lo que iba a ir mi blog, os dije que estaba intentando aprender clean architecture y Dagger 2, así que, se me ocurrió crear un ejemplo con esto desarrollado en Kotlin.

El ejemplo se encuentra en Github, lo podéis obtener aquí. KotlinSeries

Además, tengo pensado desarrollar el mismo código en Java para poder comparar entre ambos lenguajes. Sería interesante observar el número de líneas que te ahorras usando Kotlin.

MVP

La estructura del proyecto está basada en MVP + Repository. El ejemplo muestra una lista de Series que se presentan ordenadas alfabéticamente. Por el momento está lista es rellenada por un fake, es decir, no hay conexión con ningún servidor ni DB, cosa que queda como objetivo para el futuro.

No voy a entrar en detalle de como se estructura el modelo MVP, ya que hay demasiado escrito sobre ello y al final del post os dejaré unas referencias muy interesantes. Pero sí os voy a contar como lo desarrollo yo.

Mis vistas, ya sean activity o fragment, siempre tienen un presenter. Llegado el caso que la vista tenga varias funcionalidades bien distintas, podría tener un presenter por cada funcionalidad. En el proyecto cada Activity tiene un presenter que se encarga de obtener notificaciones de la vista, y devolverle la contestación necesaria.

Cada activity suele tener una funcionalidad, que viene implementada por un caso de uso. La separación de estos casos de uso es estrictamente necesaria para mantener código limpio y fácilmente escalable. El caso de uso se encargará de solicitar al repository los datos que necesita.

En este test, como los datos son fake, no hay servidor, no aparece la función del ‘DataSource’, tema que abordaré cuando le incluya el servidor para descarga de datos.

El esquema sería el siguiente:

Captura de pantalla 2016-03-09 a las 23.10.50

Kotlin vs Java

¿El post no iba de Kotlin?

Sí, en esta parte del post quería enseñaros las principales diferencias que he visto con Java.

Clases

Escribir clases con Java acaba siendo un verdadero coñazo, aunque con el generate… de Android Studio todo se simplifica. Sin embargo, las clases de objeto suelen definir varios setters/getters, además de un método equals o un toString.

En Kotlin vale con una línea para todo lo descrito anteriormente:

data class Serie(val name: String, val description: String, val image: String)

Hebras

En Java la descarga de datos se debe realizar en otra hebra que no sea la Main para no ralentizar la App. Por eso, se debe crear una nueva hebra, hacer la petición de descarga de datos, y una vez que se recibe la respuesta hay que volver a la Main Thread para mostrar los datos. En Kotlin el funcionamiento es el mismo, sólo que con bastante menos líneas de código.

fun getAll(callback : (ArrayList&lt;Serie&gt;?) -&gt; Unit) {
        async() {
            var list = repository.getAll()
            uiThread {
                callback(list)
            }
        }
    }

Esto es posible gracias a la librería Anko.

Callback

Si alguno ha podido ver ya, el método anterior recibe como parámetro un callback. En Java para poder hacer esto era necesario crear una interfaz con un método, y implementarlo donde se llamara a la función. De nuevo, Kotlin elimina código innecesario y la llamada a getAll quedaría de la siguiente forma:

getSeries.getAll(){
    if(it?.size!!.compareTo(0) &amp;gt; 0){
        view?.hideLoading()
        (view as SeriesPresenter.View).showSeries(it)
    }else{
        view?.hideLoading()
        (view as SeriesPresenter.View).showEmptyCase()
    }
}

Cuando aparece la llamada a getAll no se le pasa ningún parámetro, aunque reciba el callback comentado anteriormente. Esto es porque el corchete que se abre seguidamente representa ese callback. La palabra reservada it sirve para recibir el parámetro, en este caso un ArrayList con las Series a mostrar.

Null Safety

Durante el código aparecen ? ó ! ó incluso !!. Gracias al operador ? nos salvamos de la archiconocida NullPointerException que tanta lata da.

Conclusión

Además de estas posibilidades comentadas, Kotlin ofrece otras muchas que aún no he tenido tiempo de investigar y probar.

Me decidí a probarlo por las grandes similitudes que vi con Swift, y he quedado impresionado con las posibilidades y la facilidad en el código. Como todo lo nuevo, supone una curva de aprendizaje al principio lenta, pero es un lenguaje que merece la pena conocer.

Creo que una de sus mayores virtudes podría ser la alternancia en el mismo proyecto de código Java y código Kotlin, ya que cada uno podría ser útil en un aspecto distinto o incluso para el uso de librerías con poca compatibilidad con Kotlin seguir usándolas en Java.

Espero seguir mejorando este ejemplo, y en posteriores post hablar sobre Dagger 2 y funcionalidades nuevas a implementar.

Referencias

Kotlin – Antonio Leiva – http://antonioleiva.com/kotlin/

MVP – Antonio Leiva – http://antonioleiva.com/mvp-android/

My way to clean Android – Christian Panadero – http://panavtec.me/my-way-to-clean-android/

Nueva experiencia

Hola a todos!

Bueno, pues aquí comienza un nuevo proyecto que llevo varias semanas pensando. Me apetecía empezar a escribir un blog sobre temas de Swift y Android, compartiendo código y vivencias que he ido pasando durante los proyectos que se presentarán.

El principal objetivo de este blog es aprender a programar en esos dos lenguajes que tanto me apasionan. Cuando digo aprender a programar, me refiero a mejorar en cuanto a nivel de código, claridad y técnicas para mejorar la calidad del código.

Últimamente estoy intentando mejorar mis conocimientos de Android aprendiendo Clean architecture e inyección de dependencias con Dagger 2. Creo que los próximos posts irán sobre este tema.

Espero no defraudar!

Saludos

@josedlpozo