Ocultar las credenciales de TestFairy a ojos curiosos

Como me resulta muy molesto tener que ir actualizando el número de versión cada vez que quiero subir un apk, últimamente vengo usando TestFairy y su plugin para Gradle.

La verdad es que funciona maravillosamente bién, pero siguiendo las instrucciones de la página del plugin parece que tuvieras que subir las credenciales (apiKey) en el repositorio de código fuente.

Para ello crearemos un fichero gradle.properties. Yo lo he puesto en la ruta $HOME/.gradle/gradle.properties pero puedes ponerlo en otros sitios.

$ cat $HOME/.gradle/gradle.properties
MyProjectTestFairyApiKey=blahblahblahblah

Es interesante conocer que todas las propiedades que pongamos aquí, estarán disponibles en el script de Gradle automáticamente.

En el build.gradle dónde va la api key, ponemos el nombre de la preferencia que acabamos de definir en el otro fichero:

testfairyConfig {
	apiKey MyProjectTestFairyApiKey
	...
}

De esta forma, no tendremos que compartir las credenciales con todo el mundo que tenga acceso al código fuente.

Saludos

Utilizar Theme Light y Toolbar oscura

Anteriormente heredábamos del tema Theme.AppCompat.Light.DarkActionBar.

Al utilizar Toolbar en lugar de ActionBar es necesario heredar de un tema .NoActionBar:

<style name="Theme.MyApp" parent="@style/Theme.AppCompat.Light.NoActionBar">
    // Your theme customizations here
</style>

Para utilizar la versión oscura de la toolbar, es importante indicar el atributo app:theme, tal y como se muestra a continuación:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">    

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_alignParentTop="true"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>

</RelativeLayout>

Vista vacía de ListView y SwipeRefreshLayout

A veces, las cosas que deberían ser muy sencillas en Android, se complican.

Por ejemplo al utilizar SwipeRefreshLayout, que únicamente permite una vista hija dentro de su jerarquía, los problemas se reproducen como enanos. Por ejemplo al intentar utilizar el infame setEmptyView.

Finalmente, siguiendo esta pregunta de StackOverflow he optado por utilizar dos SwipeRefreshLayout, uno para el ListView y otro para la EmptyView.

El XML de layout quedaría así:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/ptr_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/lv_contents"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </android.support.v4.widget.SwipeRefreshLayout>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@android:id/empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/empty_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20dp"
                android:gravity="center_horizontal"
                android:text="@string/empty_list"/>

        </ScrollView>

    </android.support.v4.widget.SwipeRefreshLayout>

</FrameLayout>

El código del controlador sería algo así:

public class SomeActivity extends AppCompatActivity {

	private ListView mListView;
	private SwipeRefreshLayout mSwipeRefreshLayout, mSwipeEmpty;

	private SwipeRefreshLayout.OnRefreshListener mRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {

		@Override
		public void onRefresh() {

			//Do the refresh
		}
	};


	@Override
	public void onCreate(Bundle savedInstanceState) {


        mListView = (ListView) layout.findViewById(R.id.lv_contents);

        mSwipeEmpty = (SwipeRefreshLayout) layout.findViewById(android.R.id.empty);
		mSwipeRefreshLayout = (SwipeRefreshLayout) layout.findViewById(R.id.ptr_layout);

		configureSwipeRefreshLayout(mSwipeRefreshLayout, mRefreshListener);
		configureSwipeRefreshLayout(mSwipeEmpty, mRefreshListener);

		mListView.setEmptyView(mSwipeEmpty);
	}


	public static void configureSwipeRefreshLayout(SwipeRefreshLayout layout,
		SwipeRefreshLayout.OnRefreshListener listener) {

		layout.setOnRefreshListener(listener);
		layout.setColorSchemeResources(
		R.color.ab_swipe_color1,
		R.color.ab_swipe_color2,
		R.color.ab_swipe_color3,
		R.color.ab_swipe_color4);
	}
}

No me gusta nada tener que aplicar estas soluciones, pero a veces es lo que hay. Se agradecería una forma más elegante.

Android Product Flavors

En muchas ocasiones, cuando desarrollemos una app de Android, nos interesará generar diferentes variantes de una misma app. Por ejemplo, en el caso de nuestra aplicación deba consumir uno json proporcionado por un servicio en la red, tendrémos un servidor para pruebas y un servidor de producción.

Para ello usamos Product Flavors, una funcionalidad del sistema de construcción Gradle de Android.

En el fichero build.gradle de nuestra app añadiremos:

	productFlavors {
		
		dev {
			applicationId "com.sourcerebels.flavors.dev"
			resValue "string", "service_url", "http://dev.example.com"
		}
		production {
			applicationId "com.sourcerebels.flavors.production"
			resValue "string", "service_url", "http://www.example.com"
		}
	}

Ahora, desde la ventana “Build Variants” de Android Studio, seleccionaremos que variante queremos construir.

Tambíen podemos hacerlo con gradle desde la línea de comandos. Por ejemplo:

	gradle module:assembleDevDebug
	gradle module:assembleProductionRelease

Flavors nos permite además personalizar recursos o assets. Para ello es tan simple como crear las carpetas src/flavor/res, src/flavor/assets y src/flavor/java.

Por ejemplo, si quisiesemos personalizar los colores o un recurso de la app en función de la variante, personalizaríamos los siguientes ficheros:

	src/dev/res/values/colors.xml
	src/dev/res/drawable/some_resource.xml

	src/production/res/values/colors.xml
	src/production/res/drawable/some_resource.xml

Parece que vamos a usar mucho esta funcionalidad de ahora en adelante.

Detección contexto de test

A veces, necesitamos diferenciar si estamos ejecutando nuestro código con normalidad o, por contra, nos encontramos en un contexto de test automatizados.

Supuestamente existe un método isRunningInTestHarness para hacer esto mismo, aunque en mi experiencia no funciona.

Una alternativa posible para hacer esto sería aprovecharnos de la estructura de proyecto que propone el sistema de construcción Gradle y los recursos de Android.

Para ello, en el directorio src/main/res/values/ crearíamos un fichero xml de recursos de Android (p.ej: configuration.xml) con un boolean a false:

<bool android:name="running_in_test_harness">false</bool>

También en el directorio src/androidTest/res/values/ crearemos el mismo fichero configuration.xml con el mismo boolean pero esta vez con valor true.

<bool android:name="running_in_test_harness">true</bool>

Posteriormente podemos obtener el valor de la siguiente manera:

public static boolean isRunningInTestHarness(Context context) {
	return context.getResources().getBool(R.bool.running_in_test_harness);
}