Advertisement
  1. Code
  2. Coding Fundamentals

RxJava 2 para Apps Android: RxBinding y RxLifecycle

Scroll to top
Read Time: 17 min

() translation by (you can also view the original English article)

RxJava es una de las librerías más populares para traer programación reactive a la plataforma Android, y es esta serie de tres partes te estaré mostrando cómo comenzar a usar esta librería en tus propios proyectos Android.

En Comienza Con RxJava 2 para Android, vimos lo que es RxJava y es que ofrece a desarrolladores Android, antes de crear una aplicación Hola Mundo que demostraba los tres componentes nucleares de RxJava: un Observable, un Observer, y una suscripción.

En el tutorial Operadores de Programación Reactive en RxJava, vimos cómo hacer transformaciones complejas de datos usando operadores, y cómo puedes combinar Operators y Schedulers para finalmente hacer multi-hilos en Android una experiencia indolora.

También tocamos RxAndroid, una librería específicamente diseñada para ayudarte a usar RxJava en tus proyectos Android, pero hay mucho más que explorar en RxAndroid. Así qué, en este artículo, voy a enfocarme únicamente en la familia de librerías RxAndroid.

Muy parecido a RxJava, RxAndroid sufrió una revisión masiva en su liberación de versión 2. El equipo de RxAndroid decidió modularizar la librería, moviendo mucha de su funcionalidad a módulos complementarios dedicados RxAndroid.

En este artículo, vamos a mostrarte cómo configurar y usar algunos de los módulos RxAndroid más populares y poderosos---incluyendo una librería que puede hacer listeners, handlers y TextWatchers cosa del pasado dándote la habilidad de manejar cualquier evento UI Android como un Observable.

Y ya que las fugas de memoria ocasionadas por suscripciones incompletas son la desventaja más grande de usar RxJava en tus aplicaciones Android, también te mostraré cómo usar un módulo RxAndroid que pueda manejar el proceso de suscripción por ti. Al final de este artículo, sabrás como usar RxJava en cualquier Activity o Fragment sin correr el riesgo de encontrar cualquier fuga de memoria relacionada con Rx-Java.

Creando Más UIs Android Reactive

Reaccionar a eventos UI tales como toques, deslizamientos o entradas de texto es una parte fundamental de desarrollar prácticamente cualquier aplicación Android, ero manejar eventos Android UI no es particularmente sencillo.

Típicamente reaccionas a eventos usando una combinación de listeners, handlers, TextWatchers, y posiblemente otros componentes dependiendo del tipo de UI que estás creando. Cada uno de estos componentes requiere que escribas una cantidad significativa de código base, y para empeorar las cosas no hay consistencia en cómo implementas estos diferentes componentes. Por ejemplo, puedes manejar eventos OnClick implementando un OnClockListener:

1
Button button = (Button)findViewById(R.id.button);
2
button.setOnClickListener(new View.OnClickListener() {
3
    @Override
4
    public void onClick(View v) {
5
        //Perform some work//

6
    }
7
});

Pero esto es completamente diferente de cómo implementarías un TextWatcher:

1
final EditText name = (EditText) v.findViewById(R.id.name); 
2
//Create a TextWatcher and specify that this TextWatcher should be called whenever the EditText’s content changes// 

3
name.addTextChangedListener(new TextWatcher() {
4
    @Override
5
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6
    }
7
8
    @Override 
9
    public void onTextChanged(CharSequence s, int start, int before, int count) {
10
        //Perform some work//

11
    } 
12
13
    @Override 
14
    public void afterTextChanged(Editable s) { 
15
    }
16
 });

Esta falta de consistencia puede agregar potencialmente mucha complejidad a tu código. Y si tienes componentes UI que dependan de la salida de otros componentes UI, ¡entonces prepárate para que las cosas se compliquen aún más! Incluso un simple caso de uso---como pedir al usuario que teclee su nombre en un EditText para que puedas personalizar el texto que aparece en TextViews subsecuentes---requiere de callbacks anidados, los cuáles son notoriamente difíciles de implementar y mantener. (Alguna gente se refiere a los callbacks anidados como "infierno callback.")

Claramente, una aproximación estandarizada para manejar eventos UI tiene el potencial para simplificar tu código enormemente, y RxBinding es una librería que hace justo eso, proporcionando enlaces que te permiten convertir cualquier evento View Android en un Observable.

Una vez que has convertido un evento de vista en un Observable, este emitirá sus eventos UI como flujos de información a los que puedes suscribirte exactamente de la misma forma en la que te suscribirías a cualquier otro Observable.

Ya que ya hemos visto cómo capturar un evento de clic usando el OnClickListener estándar de Android, veamos cómo lograrías los mismos resultados usando RxBinding.

1
import com.jakewharton.rxbinding.view.RxView;
2
3
...
4
5
Button button = (Button) findViewById(R.id.button);
6
RxView.clicks(button)
7
      .subscribe(aVoid -> {
8
        //Perform some work here//

9
      });

No solo esta aproximación es más concisa, sino que es una implementación estándar que puedes aplicar a todos los eventos UI que ocurren a lo largo de tu app. Por ejemplo, capturar entradas de texto sigue el mismo patrón que capturar eventos de clic:

1
RxTextView.textChanges(editText)
2
    .subscribe(charSequence -> {
3
        //Perform some work here//

4
    });

Una App de Ejemplo Con RxBinding

Así que puedes ver exactamente como RxBinding puede simplificar el código relacionado a UI de tu app, creemos una app que demuestre unos cuantos de estos enlaces en acción. También voy a incluir un View que depende de la salida de otro View, para demostrar como RxBinding simplifica crear relaciones entre componentes UI.

Esta app va a consistir en:

  • Un Button que muestre un Toast cuando se presione.
  • Un EditText que detecta cambios de texto.
  • Un TextView que se actualiza para mostrar los contenidos del EditText.

Configuración de Proyecto

Crea un proyecto de Android Studio con los ajustes de tu elección, y después abre tu archivo build.gradle a nivel de módulo y agrega la última versión de la librería RxBinding como una dependencia de proyecto. En los intereses de mantener el código base al mínimo, también voy a usar lambdas, así que actualicé mi archivo build.gradle para soportar esta característica de Java 8:

1
apply plugin: 'com.android.application'
2
android {
3
      compileSdkVersion 25
4
      buildToolsVersion "25.0.2"
5
      defaultConfig {
6
          applicationId "com.jessicathornsby.myapplication"
7
          minSdkVersion 23
8
          targetSdkVersion 25
9
          versionCode 1
10
          versionName "1.0"
11
          testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12
13
//Enable the Jack toolchain//
14
15
          jackOptions {
16
              enabled true
17
          }
18
      }
19
20
      buildTypes {
21
          release {
22
              minifyEnabled false
23
              proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24
          }
25
      }
26
27
//Set sourceCompatibility and targetCompatibility to JavaVersion.VERSION_1_8//
28
29
      compileOptions {
30
          sourceCompatibility JavaVersion.VERSION_1_8
31
          targetCompatibility JavaVersion.VERSION_1_8
32
      }
33
  }
34
35
  dependencies {
36
      compile fileTree(dir: 'libs', include: ['*.jar'])
37
      androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
38
          exclude group: 'com.android.support', module: 'support-annotations'
39
      })
40
41
//Add the core RxBinding library//
42
43
      compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
44
      compile 'com.android.support:appcompat-v7:25.3.0'
45
46
//Don’t forget to add the RxJava and RxAndroid dependencies//
47
48
      compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
49
      compile 'io.reactivex.rxjava2:rxjava:2.0.5'
50
      testCompile 'junit:junit:4.12'
51
  }
52
}

Si estás trabajando con múltiples librerías RxJava, es posible que encuentres un mensaje de error Duplicate files copied in APK META-INF/DEPENDENCIES en tiempo de compilación. Si encuentras este error, entonces la solución es suprimir estos archivos duplicados agregando lo siguiente a tu archivo build.gradle a nivel de módulo:

1
android {
2
      packagingOptions {
3
4
//Use “exclude” to point at the specific file (or files) that Android Studio is complaining about//
5
6
          exclude 'META-INF/rxjava.properties'
7
      }

Crea el Layout de Main Activity

Sincroniza tus archivos Gradle, y después crea un layout consistente de un Button, un EditText, y un TextView:

1
<?xml version="1.0" encoding="utf-8"?>
2
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
3
  xmlns:tools="http://schemas.android.com/tools"
4
  android:layout_width="match_parent"
5
  android:layout_height="match_parent"
6
  android:orientation="vertical"
7
  tools:context=".MainActivity" >
8
9
  <Button
10
      android:text="Button"
11
      android:layout_width="wrap_content"
12
      android:layout_height="wrap_content"
13
      android:id="@+id/button" />
14
15
  <EditText
16
      android:layout_width="wrap_content"
17
      android:layout_height="wrap_content"
18
      android:inputType="textPersonName"
19
      android:text="Type here"
20
      android:ems="10"
21
      android:id="@+id/editText" />
22
23
  <TextView
24
      android:text="TextView"
25
      android:layout_width="match_parent"
26
      android:layout_height="wrap_content"
27
      android:id="@+id/textView" />
28
29
</LinearLayout>

Codifica los Enlaces de Evento

Ahora veamos cómo usarías estos RxBinding para capturar los diferentes eventos UI a los que nuestra aplicación necesita reaccionar. Para comenzar, declara tus imports y define la clase MainActivity.

1
package com.jessicathornsby.myapplication;
2
3
import android.os.Bundle;
4
import android.support.v7.app.AppCompatActivity;
5
import android.widget.Button;
6
import android.widget.EditText;
7
import android.widget.TextView;
8
import android.widget.Toast;
9
10
//Import the view.RxView class, so you can use RxView.clicks//

11
12
import com.jakewharton.rxbinding.view.RxView;
13
14
//Import widget.RxTextView so you can use RxTextView.textChanges//

15
16
import com.jakewharton.rxbinding.widget.RxTextView;
17
18
public class MainActivity extends AppCompatActivity {
19
  @Override
20
  protected void onCreate(Bundle savedInstanceState) {
21
      super.onCreate(savedInstanceState);
22
      setContentView(R.layout.activity_main);
23
    
24
      Button button = (Button) findViewById(R.id.button);
25
      TextView textView = (TextView) findViewById(R.id.textView);
26
      EditText editText = (EditText) findViewById(R.id.editText);
27
28
      //Code for the bindings goes here//

29
      //...//

30
  }
31
}

Ahora puedes comenzar a agregar enlaces para responder a eventos UI. El método RxView.clicks es usado para enlazar eventos clic. Crea un enlace para mostrar un toast siempre que el botón sea presionado.

1
        RxView.clicks(button)
2
            .subscribe(aVoid -> {
3
                Toast.makeText(MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT).show();
4
            });

Después, usa el método RxTextView.textChanges() para reaccionar al evento de cambio de texto actualizando el TextView con los contenidos de nuestro EditText.

1
        RxTextView.textChanges(editText)
2
            .subscribe(charSequence -> {
3
                textView.setText(charSequence);
4
            });

Cuando ejecutes tu app, terminarás con una pantalla como la siguiente.

The default version of our RxBinding user interface The default version of our RxBinding user interface The default version of our RxBinding user interface

Instala tu proyecto en un smartphone Android físico o tablet o un AVD compatible, y después pasa algún tiempo interactuando con los distintos elementos UI. Tu app debería reaccionar a eventos clic y entrada de texto de manera normal---¡y todo sin un listener, TextWatcher o callback a la vista!

RxBinding user interfaceRxBinding user interfaceRxBinding user interface

RxBinding para Vistas de Librería de Soporte

Mientras que la librería nuclear RxBinding proporciona enlaces para todos los elementos UI que conforman la plataforma estándar Android, también hay módulos RxBinding hermanos que proporcionan enlaces para las Vistas que están incluídas como parte de las distintas librerías de soporte de Android.

Si has agregado una o más librerías de soporte a tu proyecto, entonces típicamente querrás agregar el módulo RxBinding correspondiente también.

Estos módulos hermanos siguen convención de nomenclatura que hace sencillo identificar la librería de soporte de Android correspondiente:  cada módulo hermano simplemente toma el nombre de la librería de soporte, y reemplaza com.android con com.jakewharton.rxbinding2:rxbinding.

  • compile com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
  • compile 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
  • compile 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
  • compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
  • compile 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
  • compile 'com.jakewharton.rxbinding2:rxbinding-leanback-v17:2.0.0'

Si estás usando Kotlin en tus proyectos Android, entonces también hay una versión Kotlin disponible para cada módulo RxBinding. Para acceder a la versión Kotlin, simplemente adjunta -kotlin al nombre de la librería con la que quieres trabajar, así:

1
compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'

Se vuelve:

1
compile 'com.jakewharton.rxbinding2:rxbinding-design-kotlin:2.0.0'

Una vez que has convertido un evento View a un Observable, todos esos eventos son emitidos como un flujo de datos. Como hemos visto, puedes suscribir a estos flujos y después realizar cualquier tarea que necesites para este evento UI particular a disparar, tal como mostrar un Toast o actualizar un TextView. Sin embargo, también puedes aplicar cualquiera de los operadores de la enorme colección de RxJava a este flujo observable, e incluso encadenar múltiples operadores para realizar transformaciones complejas en tus eventos UI.

Hay demasiados operadores para discutir en un solo artículo (y la documentación oficial lista todos los operadores de cualquier modo) pero cuando se trata de trabajar con eventos UI Android, hay algunos cuantos operadores que pueden resultar particularmente útiles.

El Operador debounce()

Primeramente, si estás preocupado de que un usuario impaciente presione repetidamente un elemento UI, potencialmente confundiendo a tu app, entonces puedes usar el operador debounce() para filtrar cualquier evento  UI que sea emitido en rápida sucesión.

En el siguiente ejemplo, estoy especificando que este botón debería reacción a un evento OnClick solo si ha habido al menos una brecha de 500 milisegundos desde el evento clic anterior:

1
RxView.clicks(button)
2
    .debounce(500, TimeUnit.MILLISECONDS)
3
    .subscribe(aVoid -> {
4
      Toast.makeText(MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT).show();
5
    });

El Operador publish()

También puedes usar el operador publish() para adjuntar múltiples listeners a la misma vista, algo que ha sido tradicionalmente difícil de implementar en Android.

El operador publish() convierte un Observable estándar en un observable connectable. Mientras que un observable regular comienza a emitir elementos tan pronto como el primer observador se suscribe a el, un observable connectable no emitirá nada hasta que se lo instruyas explícitamente, aplicando el operador connect(). Esto te da una ventana de oportunidad en la cuál puedes suscribir varios observadores, sin que el observable comience a emitir elementos tan pronto la primer suscripción suceda.

Una vez que has creado todas tus suscripciones, simplemente aplica el operador connect() y el observable comenzará a emitir datos a todos sus observadores asignados.

Evita Fugas de Memoria que Colapsen la App

Como vimos a lo largo de la serie, RxJava puede ser una poderosa herramienta para crear aplicaciones Android más reactivas e interactivas, con mucho menos código del que típicamente necesitarías para obtener los mismos resultados usando solo Java. Sin embargo, hay una gran desventaja en usar RxJava en tus aplicaciones Android---el potencial para las fugas de memoria causado por suscripciones incompletas.

Estas fugas de memoria ocurren cuando el sistema Android intenta destruir una Activity que contiene un Observable en ejecución. Ya que el observable se está ejecutando, su observador aún estará reteniendo una referencia a la actividad, y el sistema no será capaz de recolectar basura en esta actividad como resultado.

Ya que Android destruye y re-crea Activitys cada vez que la configuración del dispositivo cambia, tu app podría estar creando una Activity duplicada cada vez que el usuario cambia entre modo retrato y panorámico, así como cada vez que abren y cierran el teclado de su dispositivo.

Estas actividades estarán en segundo plano, potencialmente sin recolectar nunca basura. Ya que las Activities son objetos grandes, esto puede llevar rápidamente a problemas serios de administración de memoria, especialmente ya que los teléfonos Android y tablets tienen memoria limitada para empezar. La combinación de una fuga grande de memoria y memoria limitada puede resultar rápidamente en un error Out Of Memory.

Las fugas de memoria RxJava pueden tener el potencial para causar estragos en el desempeño de tu aplicación, pero hay una librería RxAndroid que te permite usar RxJava en tu app sin tener que preocuparte sobre fugas de memoria.

La librería RxLifeCycle, desarrollada por Trello, proporciona APIs de manejo de ciclo de vida que puedes usar para limitar la vida de un Observable al ciclo de vida de un Activity o Fragment. Una vez que esta conexión está hecha, RxLifecycle terminará la secuencia del observable en respuesta a los eventos del ciclo de vida que ocurran en el activity o fragment asignado a ese observable. Esto significa que puedes crear un observable que termine automáticamente siempre que un activity o fragment es destruido.

Nota que estamos hablando de terminar una secuencia, y no cancelar suscripción. Aunque frecuentemente se habla sobre  RxLifecycle en el contexto de administrar el proceso de suscripción/cancelar suscripción, técnicamente no cancela la suscripción de un observador.  En su lugar, la librería RxLifecycle termina la secuencia observable emitiendo ya sea el método onComplete() o onError(). Cuando cancelas la suscripción, el observador deja de recibir notificaciones de su observable, incluso si ese observable aún está emitiendo elementos. Si requieres específicamente comportamiento de cancelación de suscripción, entonces eso es algo que necesitarás implementar tu mismo.

Usando RxLifecycle

Para usar RxLifeCycle en tus proyectos Android, abre tu archivo build.gradle a nivel módulo y agrega la última versión de la librería RxLifecycle, más la librería Android RxLifecycle:

1
dependencies {
2
  ...
3
  ...
4
compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1'
5
compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.0.1'

Después, en la Activity o Fragment en donde quieres usar las APIs de manejo de la librería lifecycle, extiende ya sea RxActivityRxAppCompatActivity o RxFragment, y agrega la declaración de importación correspondiente, por ejemplo:

1
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
2
3
...
4
5
public class MainActivity extends RxAppCompatActivity {

Cuando se trata de enlazar un Observable al ciclo de vida de un Activity o Fragment, puedes especificar el evento lifecycle en donde el observable debería terminar, o puedes dejar que la librería RxLifecycle decida cuándo debería terminar la secuencia observable.

Por defecto, RxLifecycle terminará un observable en el evento de ciclo de vida complementario en donde ocurrió la suscripción, a´si que si suscribes a un observable durante el método onCreate() de tu Activity, entonces RxLifecycle terminará la secuencia observable durante el método onDestroy() de esa Activity. Si suscribes durante el método onAttach() de un Fragment, entonces RxLifecycle terminará la secuencia en el método onDetach().

Puedes dejar esta decisión a RxLifecycle, usando RxLifecycleAndroid.bindActivity:

1
Observable<Integer> myObservable = Observable.range(0, 25);
2
3
...
4
5
  @Override
6
  public void onResume() {
7
      super.onResume();
8
      myObservable
9
              .compose(RxLifecycleAndroid.bindActivity(lifecycle))
10
              .subscribe();
11
  }

De manera alternativa, puedes especificar el evento de ciclo de vida en donde RxLifecycle debería terminar una secuencia Observable, usando RxLifecycle.bindUntilEvent.

Aquí, estoy especificando que la secuencia observable debería ser terminada en onDestroy():

1
@Override
2
public void onResume() {
3
  super.onResume();
4
  myObservable
5
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
6
    .subscribe();
7
}

Trabajando con Permisos Android Marshmallow

La librería final que vamos a ver es RxPermissions, que fue diseñada para ayudarte a usar RxJava con el nuevo modelo de permisos introducidos en Android 6.0. Esta librería también te permite hacer una petición de permiso y manejar el resultado del permiso en la misma ubicación, en lugar de solicitar el permiso en un lugar y después manejar sus resultados de manera separada, en Activity.onRequestPermissionsResult().

Comienza agregando la librería RxPermissions a tu  archivo build.gradle:

1
compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.3@aar'

Después, crea una instancia de RxPermissions:

1
RxPermissions rxPermissions = new RxPermissions(this);

Después estás listo para comenzar a hacer peticiones de permisos vía la librería RxPermissions, usando la siguiente fórmula:

1
rxPermissions.request(Manifest.permission.READ_CONTACTS)
2
   .subscribe(granted -> {
3
       if (granted) {           
4
          // The permission has been granted// 

5
       } else {
6
         // The permission has been denied//

7
       }
8
   });

En dónde asignas tus peticiones de permisos es crucial, ya que siempre hay una posibilidad de que la Activity anfitrión sea destruída y después recreada mientras que el diálogo de permisos está en pantalla, usualmente debido a un cambio de configuración tal como el usuario moviéndose entre modos horizontal y vertical. Si esto ocurre, entonces su suscripción podría no ser recreada, lo que significa que no serás suscrito al observable de RxPermissions y no recibirás la respuesta del usuario al diálogo de petición de permiso. Para garantizar que tu aplicación reciba la respuesta del usuario, siempre invoca tu petición durante una fase de inicialización tal como Activity.onCreate()Activity.onResume(), or View.onFinishInflate().

No es poco común que las características requieran varios permisos. Por ejemplo, enviar un mensaje SMS usualmente requiere que tu app tenga los permisos SEND_SMS and READ_CONTACTS. La librería RxPermissions proporciona un método conciso de hacer múltiples peticiones de permisos, y después combinar las respuestas de los usuarios en una sola respuesta false (uno o más permisos fueron denegados) o true (todos los permisos fueron concedidos) a la que puedes reaccionar después de manera acorde.

1
RxPermissions.getInstance(this)
2
   .request(Manifest.permission.SEND_SMS,
3
            Manifest.permission.READ_CONTACTS)
4
   .subscribe(granted -> {
5
       if (granted) {
6
          // All permissions were granted//

7
       } else {
8
          //One or more permissions was denied//

9
       }
10
   });

Típicamente querrás disparar una petición de permiso en respuesta a un evento UI, tal como el usuario presionando un elemento de menú o botón, así que RxPermissions y RxBinding son dos librerías que funcionan particularmente bien juntas.

Manejar el evento UI como un observable y hacer la petición de permiso vía RxPermissions te permite hacer mucho trabajo con solo unas cuántas líneas de código:

1
RxView.clicks(findViewById(R.id.enableBluetooth))    
2
   .compose(RxPermissions.getInstance(this).ensure(Manifest.permission.BLUETOOTH_ADMIN))
3
   .subscribe(granted -> {
4
       // The ‘enableBluetooth’ button has been clicked//

5
   });

Conclusión

Después de leer este artículo, tienes algunas ideas sobre como cortar mucho código base de tus aplicaciones Android---usando RxJava para manejar todos los eventos UI de tu aplicación, y haciendo tus peticiones de permisos vía RxPermissions. También vimos cómo puedes usar RxJava en cualquier Activity o Fragment de Android, sin tener que preocuparte por las fugas de memoria que podrían ser causadas por suscripciones incompletas.

Hemos explorado algunas de las librerías RxJava y RxAndroid más importantes en esta serie, pero si tienes curiosidad de ver qué más tiene que ofrecer RxJava a desarrolladores Android, revisa algunas de las muchas otras librerías RxAndroid. Encontrarás una lista exhaustiva de librerías RxAndroid adicionales en GitHub.

Mientras tanto, ¡revisa algunas de nuestras otras publicaciones de desarrollo Androis aquí en Envato Tuts+!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.