Gradle. Buscar nuevas versiones de las librerías que usa tu proyecto

La tarea de buscar manualmente si existen nuevas versiones de las librerías que usamos en un proyecto puede ser muy tediosa. El plugin de gradle gradle-versions-plugin nos permite elaborar un informe acerca de las nuevas versiones de librerías que usemos.

En el fichero build.gradle de la carpeta raíz de nuestro proyecto Gradle tendremos que:

1. Asegurarnos que tiene definido el repositorio jcenter.
2. Añadir, en el apartado buildscript -> dependencies la dependencia del plugin gradle-versions.
3. En la sección allprojects indicar que este plugin estará disponible para todos los proyectos.

El siguiente trozo de código muestra las líneas implicadas.

buildscript {
    
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
    }
}

allprojects {

    apply plugin: "com.github.ben-manes.versions"
}

A continuación, ya podemos extraer un informe donde se nos indicará qué librerías que estamos utilizando disponen de una versión más actual a la utilizada por nuestro proyecto. En mi caso, como se trata de un proyecto Android lo haré sobre el módulo app.

./gradlew app:dependencyUpdates

La salida de este comando para mi proyecto es la siguiente:

> Task :app:dependencyUpdates 

------------------------------------------------------------
:app Project Dependency Updates (report to plain text file)
------------------------------------------------------------

The following dependencies are using the latest milestone version:
 - android.arch.persistence.room:compiler:1.1.1
 - android.arch.persistence.room:runtime:1.1.1
 - com.android.support:appcompat-v7:28.0.0
 - com.android.support:cardview-v7:28.0.0
 - com.android.support:design:28.0.0
 - com.android.support.test:runner:1.0.2
 - com.android.support.test.espresso:espresso-core:3.0.2
 - com.github.PhilJay:MPAndroidChart:v3.1.0-alpha
 - com.google.code.gson:gson:2.8.5
 - com.google.truth:truth:0.42
 - com.google.truth.extensions:truth-java8-extension:0.42
 - com.jakewharton:butterknife:9.0.0-rc1
 - com.jakewharton:butterknife-compiler:9.0.0-rc1
 - junit:junit:4.12
 - org.eclipse.paho:org.eclipse.paho.android.service:1.1.1
 - org.mockito:mockito-core:2.23.0

The following dependencies have later milestone versions:
 - com.android.support.constraint:constraint-layout [1.1.3 -> 2.0.0-alpha2]
     http://tools.android.com
 - com.android.tools.build:aapt2 [3.2.1-4818971 -> 3.4.0-alpha03-5013011]
     https://developer.android.com/studio
 - com.android.tools.lint:lint-gradle [26.2.1 -> 26.4.0-alpha03]
     https://developer.android.com/studio
 - com.google.dagger:dagger-android [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-android-processor [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-android-support [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-compiler [2.15 -> 2.19]
     https://github.com/google/dagger
 - io.reactivex.rxjava2:rxandroid [2.0.2 -> 2.1.0]
     https://github.com/ReactiveX/RxAndroid
 - io.reactivex.rxjava2:rxjava [2.1.16 -> 2.2.3]
     https://github.com/ReactiveX/RxJava
 - org.eclipse.paho:org.eclipse.paho.client.mqttv3 [1.1.1 -> 1.2.0]
     http://www.eclipse.org/paho

Gradle updates:
 - Gradle: [4.6 -> 4.10.2 -> 5.0-rc-3]

Generated report file build/dependencyUpdates/report.txt


BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed

Suscribirse a un topic MQTT desde Android

Para la conexión utilizaremos la librería Eclipse Paho Android Service.

Configuración Gradle

En el fichero `build.gradle` de nuestro proyecto Android será necesario añadir las siguientes dependencias:

    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
    implementation('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') {
        exclude group: 'com.android.support'
        exclude module: 'appcompat-v7'
        exclude module: 'support-v4'
    }

Servidor MQTT de pruebas (Mosquitto)

Dockerfile

FROM eclipse-mosquitto:1.4.12
ADD mosquitto.conf /mosquitto/config/mosquitto.conf

mosquito.conf

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

docker-compose.yml

version: '3'
services:
  mqttbroker:
    build: .
    image: test-mosquitto:0.0.1-SNAPSHOT
    container_name: test.mosquitto.container
    ports:
      - 9001:9001
      - 1883:1883

Para levantar el servicio MQTT será suficiente con ejecutar `docker-compose up -d` en la ruta donde se encontrasen los archivos anteriores.

Código Android

La aplicación de prueba consiste en una activity vacía que se suscribe al topic `test-topic` y muestra los mensajes recibidos en el log de Android.
AndroidManifest.xml
En el fichero AndroidManifest.xml será necesario añadir los siguiente permisos:

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

También habrá que añadir el servicio `MqttService` proporcionado por la librería:

<service android:name="org.eclipse.paho.android.service.MqttService" />

MainActivity.java

package org.eurecat.test.mqtt.android;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.UUID;

public class MainActivity extends AppCompatActivity implements MqttCallback, IMqttActionListener {

    private static final String SERVER_URI = "tcp://10.42.0.1:1883";
    private static final String TOPIC = "test-topic";
    private static final int QOS = 1;
    private static final String TAG = "MainActivity";

    private MqttAndroidClient mqttAndroidClient;

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

        String clientId = UUID.randomUUID().toString();
        Log.d(TAG, "onCreate: clientId: " + clientId);

        mqttAndroidClient = new MqttAndroidClient(getApplicationContext(), SERVER_URI, clientId);
        mqttAndroidClient.setCallback(this);

        MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
        mqttConnectOptions.setCleanSession(false);


        try {
            Log.d(TAG, "onCreate: Connecting to " + SERVER_URI);
            mqttAndroidClient.connect(mqttConnectOptions, null, this);
        } catch (MqttException ex){
            Log.e(TAG, "onCreate: ", ex);
        }
    }

    @Override
    public void onSuccess(IMqttToken asyncActionToken) {
        Log.d(TAG, "onSuccess: ");
        try {
            mqttAndroidClient.subscribe(TOPIC, QOS);
        } catch (Exception e) {
            Log.e(TAG, "Error subscribing to topic", e);
        }
    }

    @Override
    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
        Log.e(TAG, "Failed to connect to: " + SERVER_URI, exception);
    }

    @Override
    public void connectionLost(Throwable cause) {
        Log.d(TAG, "connectionLost: ", cause);
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        Log.d(TAG, "Incoming message: " + new String(message.getPayload()));
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        Log.d(TAG, "deliveryComplete: ");
    }

}

Pruebas

En Ubuntu, es posible instalar el cliente ejecutando:

sudo apt install mosquitto-clients

Para enviar un mensaje al topic `test-topic` ejecutaremos:

mosquitto_pub -h localhost -t test-topic -m 'test mqtt message'

Esta es la salida del log de android:

10-26 13:44:05.679 24785 24785 D MainActivity: Incoming message: test mqtt message

Obtener información de un APK

Necesitamos utilizar la herramienta aapt (Android Asset Packaging Tool).

  • Entrar en el directorio donde se encuentra el SDK de Android:

$ cd $ANDROID_SDK_HOME

  • Localizar el comando “aapt”:

$ find . -name 'aapt'
./build-tools/26.0.2/aapt
./build-tools/25.0.2/aapt
./build-tools/25.0.3/aapt
./build-tools/23.0.3/aapt
./build-tools/26.0.1/aapt
./build-tools/24.0.3/aapt
./build-tools/24.0.1/aapt
./build-tools/24.0.2/aapt

  • Obtener información del APK:

$ ./build-tools/26.0.2/aapt dump badging /path/to/some.apk

La salida de este comando nos da diferente información (nombre del package, versiones de Android, permisos requeridos, …).

package: name='com.example.android.contactmanager' versionCode='1' versionName='1.0' platformBuildVersionName=''
sdkVersion:'5'
targetSdkVersion:'5'
uses-permission: name='android.permission.GET_ACCOUNTS'
uses-permission: name='android.permission.READ_CONTACTS'
uses-permission: name='android.permission.WRITE_CONTACTS'
application-label:'Contact Manager'
application-icon-120:'res/drawable-ldpi/icon.png'
application-icon-160:'res/drawable-mdpi/icon.png'
application-icon-240:'res/drawable-hdpi/icon.png'
application: label='Contact Manager' icon='res/drawable-mdpi/icon.png'
application-debuggable
launchable-activity: name='com.example.android.contactmanager.ContactManager' label='Contact Manager' icon=''
uses-permission: name='android.permission.READ_CALL_LOG'
uses-implied-permission: name='android.permission.READ_CALL_LOG' reason='targetSdkVersion < 16 and requested READ_CONTACTS' uses-permission: name='android.permission.WRITE_CALL_LOG' uses-implied-permission: name='android.permission.WRITE_CALL_LOG' reason='targetSdkVersion < 16 and requested WRITE_CONTACTS' feature-group: label='' uses-feature: name='android.hardware.faketouch' uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' main other-activities supports-screens: 'small' 'normal' 'large' supports-any-density: 'true' locales: '--_--' densities: '120' '160' '240'

JitPack.io mola mucho

Hace ya un tiempo que vengo siguiendo este servicio (he visto que mucha gente lo va utilizando). No voy explicar nada del otro mundo, simplemente quiero recomendar su uso.

JitPack.io es un servicio que permite publicar un tag concreto de un repositorio de Github en su repositorio de Maven.

El esfuerzo necesario para publicar i/o utilizar una librería de Android es tan poco que parece mentira.

Este repositorio es compatible con los sistemas de construcción: Maven (Java), Gradle (Java, Android, Groovy), SBT (Scala) o Leiningen (Clojure).

Mola mucho. Buen trabajo 🙂

Mostrar el resultado de los test unitarios al ejecutar Gradle

Una de las cosas que me confunde cuando ejecuto el Gradle desde la línea de comandos es que si los tests se ejecutan sin fallos, no muestra ningún tipo de detalle acerca de que tests fueron ejecutados. Con lo que no acabo de tener la certeza de que todo haya ido bien.

Al añadir estas líneas en el fichero build.gradle de nuestro módulo mostrará la información de qué test ha ejecutado y cual ha sido el resultado, además de la salida standard y error si la hubiese.

android {
  ...
  testOptions.unitTests.all {
    ignoreFailures false
    testLogging {
      events "passed", "skipped", "failed", "standardOut", "standardError"
    }
  }
}

Por supuesto, yo no he inventado nada. Lo saqué de esta respuesta en Stackoverflow.

Tema relacionado: Test from the Command Line

Crear el lanzador de Android Studio en GNU/Linux

Me gusta poder ejecutar las apps que tengo instaladas desde el lanzador de aplicaciones de gnome (unity, o en mac spotlight) sin ayuda del ratón, únicamente teclando su nombre.

Hasta ahora, en GNU/Linux, venía utilizando menulibre para crear manualmente la entrada de Android Studio (o cualquier otro programa no instalado vía gestor de paquetes del sistema).

Hoy he descubierto que existe una opción de menú para hacerlo:

tools -> Create Desktop Entry...

Bueno es saberlo 😀

img_20160930_073546

Centralizar las versiones de las librerías utilizadas por Gradle

Cuando tenemos un proyecto con varios módulos, a veces, nos interesa tener un lugar centralizado dónde definir las versiones de las librerías que utilizaremos.

De esta manera, si varios módulos del proyecto, utilizan la misma librería nos aseguraremos que todos estén usando la misma versión.

En el fichero build.gradle de la carpeta raíz, en el apartado “ext”, definiremos las versiones a utilizar:

ext {
    supportLibrary = '24.2.0'
    googlePlayServices = '9.4.0'
    junit = '4.12'
}

En el fichero build.gradle de cada uno de los módulos, en el apartado “dependencies”, haremos referencia a las versiones definidas de la siguiente manera:

dependencies {

    def versions = rootProject.ext

    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile "junit:junit:" + versions.junit
    androidTestCompile "junit:junit:" + versions.junit
    compile "com.android.support:appcompat-v7:" + versions.supportLibrary
    compile "com.android.support:design:" + versions.supportLibrary
    compile "com.google.android.gms:play-services-maps:" + versions.googlePlayServices
    compile "com.google.android.gms:play-services-location:" + versions.googlePlayServices
}

Abrir la navegación en la app de Google Maps con un Intent

La documentación se puede consultar en la página Google Maps Intents.

public static void navigateExternalTo(Context context, double latitude, double longitude) {
    String uri = String.format(Locale.ENGLISH, "google.navigation:q=%1$f,%2$f", latitude, longitude);
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
    intent.setPackage("com.google.android.apps.maps");
    context.startActivity(intent);
}