Ricevere notifiche per onPause e onResume utilizzando React-Native su Android

Potrebbe capitarvi di utilizzare React-Native su piattaforma Android e di dover necessariamente gestire la vostra App in situazioni limite come, per esempio, l’uscita momentanea (onPause) e la riattivazione successiva alla chiusura (onResume).

Pur non presentando alcun metodo nativo (JS) che consenta di accedere al controllo dello stato dell’applicazione, React-Native offre tutti gli strumenti necessari per poter gestire tale casistica senza troppi problemi.

La soluzione è un modulo nativo personalizzato che si aggancia agli eventi del dispositivo e funge da mediatore tra il contesto Javascript e il processo dell’applicazione nativa consentendo la ricezione di notifiche rispetto al cambiamento di stato dell’app (messa in pausa o ripresa).

Per prima cosa dobbiamo realizzare una classe Java che lavori come modulo React-Native:

public class DeviceEventsModule 
  extends ReactContextBaseJavaModule implements LifecycleEventListener {

  @Override 
  public void onHostResume() {
    sendEvent("onResume");
  }

  @Override
  public void onHostPause() {
    sendEvent("onPause");
  }

  @Override
  public void onHostDestroy() {
    sendEvent("onDestroy");
  }

  private void sendEvent(String eventName) {
    // ... TO BE ... 
  }
}

L’oggetto realizzato estende la classe base ReactContextBaseJavaModule e implementa l’interfaccia LifecycleEventListener. L’utilizzo dell’interfaccia LifecycleEventListener richiede l’implementazione dei metodi onHostResume, onHostPause e onHostDestroy. Questi sono metodi invocati in corrispondenza degli eventi sopracitati. In ognuno di questi eventi ho aggiunto la chiamata ad un ulteriore metodo, sendEvent, di cui vi ripropongo di seguito l’implementazione:

private void sendEvent(String eventName) {
  ReactApplicationContext appContext = getReactApplicationContext();
  appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit(eventName, null);
}

Questo non fa altro che attivare una comunicazione con l’oggetto react preposto all’inoltro delle notifiche al thread JS e, quando invocato, invia una notifica dell’evento specificato eventName.

Ora è necessario rendere disponibile questo modulo all’interno della nostra applicazione, sotto forma di modulo JS, questo lo si fa realizzando un ReactPackage:

public class AppPackage implements ReactPackage {
  @Override 
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(new DeviceEventsModule(reactContext));
  }
}

Successivamente è opportuno registrare il pacchetto all’avvio dell’applicazione, all’interno dell’oggetto MainApplication, andando a modificare il metodo getPackages:

@Override
protected List<ReactPackage> getPackages() {
  return Arrays.asList(
    new MainReactPackage(),
    new AppPackage()
  );
}

Terminato lo sviluppo nativo, possiamo finalmente integrate, all’interno della nostra app, la gestione degli eventi di sospensione e ripresa dell’applicazione come mostrato in questo esempio:

import { 
  NativeModules
} from 'react-native';
let DeviceEvents = NativeModules.DeviceEvents;

class App extends React.Component {
  componentDidMount() {
    this.onPauseListener = DeviceEvents.addListener('onPause', () => {
      console.warn('Suspending app...');
      // La tua logica di sospensione qui!
    });
    this.onResumeListener = DeviceEvents.addListener('onResume', () => {
      console.warn('Resuming app...');
      // La tua logica di ripresa qui!
    });
  }
  componentWillUnmount() {
    // E' importante stoppare i listener quando il componente
    // è distrutto da React.
    this.onPauseListener.remove();
    this.onResumeListener.remove();
  }
}

L’esempio riportato inizializza i Listener nella fase di creazione del componente e li distrugge nella fase di distruzione dello stesso. Nel momento in cui l’app va in pausa o viene ripresa successivamente alla sospensione, riceviamo notifica di questo cambio di stato attraverso il nostro modulo personalizzato.

Questa guida si basa sulla documentazione tecnica disponibile ai seguenti link:

Caricare immagini da file system in React-Native

Immaginate di avere accesso ad un set di informazioni, tra le quali, figura il percorso fisico di un’immagine, presente sul dispositivo Smartphone, che dovete caricare all’interno della vostra applicazione. Normalmente siamo abituati ad utilizzare l’oggetto Image di React-Native con immagini locali, cioè presenti all’interno del nostro progetto, oppure con URI che puntano a file remoti (probabilmente sul web), come in questi esempi:

// Local project image:
<Image source={require('../assets/images/close.png') />

// Remote image: 
<Image source={{ uri: 'http://robertoconterosito.com/close.png' }} />

Cosa fare invece se l’immagine è proprio sul nostro dispositivo?
Se per esempio, abbiamo la necessità di mostrare la seguente immagine:

var localFile = '/data/user/temp/close.png'

E’ davvero questione di dettagli ma, come sempre, è fondamentale saperlo, in caso contrario la situazione è comunque complicata!
In questo caso, se utilizzassimo require non riusciremmo ad ottenere nulla, l’accorgimento da adottare invece è davvero semplice:

<Image source={{ uri: "file://" + localFile }} />

Aggiungendo come prefisso file:// costringiamo React ad attivarsi perché carichi l’immagine dallo storage locale e non più da un percorso remoto o relativo (interno alla nostra app).