Skip to main content

Intégrer crashlytics de firebase à votre application React Native.

Le “crash reporting” vous connaissez ? C’est une des notions les plus importantes afin d’assurer la bonne qualité de votre application.

Les bugs et crashes qui appairaissent dans votre application une fois déployée, peuvent être très difficile à taquer. Vous ne serez sûrement pas en contact avec les utilisateurs afins qu’ils puissent vous détailler ce qu’il s’est passé. Et encore pire, l’utilisateur vous laissera sûrement une mauvaise note.

Dans ce tutoriel, nous allons voir comment intégrer le module crashlytics à votre applicatin react native.

Ce tutoriel nécessite que vous ayez complété notre première partie sur l’initialisation de firebase avec React Native.

Installation

Le module @react-native-firebase/crashlytics propose des fonctionnalités utiles pour les développeurs.

Créer le projet dans la console Firebase

Allez sur la console Firebase, assurez-vous que votre projet est créé, sinon suivez l’article précédent.

Intégrer @react-native-firebase/crashlytics

Exécutez dans un terminal dans votre projet :

yarn add @react-native-firebase/crashlytics
cd ios/ && pod install

 

Pour la partie android, dans `android/build.gradle` :

// ..
buildscript {
  // ..
  dependencies {
    // ..
    classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
  }
  // ..
}

 

Puis build à nouveau le projet avec :

npx react-native run-android

 

Tester votre configuration

Afin d’être sûr que le paramétrage fonctionne, créez d’abord un fichier `firebase.json` à la racine de votre projet, en y ajoutant :

{
  "react-native": {
    "crashlytics_auto_collection_enabled": true,
    "crashlytics_debug_enabled": true, // c'est temporaire, on va remettre à false plus tard 
    "crashlytics_javascript_exception_handler_chaining_enabled": false
  }
}

 

`crashlytics_debug_enabled` permet de dire à Crashlytics d’également tracker les bugs en mode debug. C’est important pour notre premier test.

Ensuite, soit lors d’un clic de bouton, soit au chargement d’un screen, insérez ce bout de code :

import crashlytics from '@react-native-firebase/crashlytics';

// ..

crashlytics().crash();

 

Allez ensuite sur la console Firebase, puis attendez 1 ou 2 minutes pour voir apparaître votre erreur :

console firebase

Testez la manip’ sur les 2 simulateurs iOS et android pour être sûr que tout fonctionne.
Maintenant, on peut passer aux cas pratiques !

Intégration d’un ErrorBoundary

Explication

Crashlytics va maintenant attraper les erreurs qui proviennent dans votre application, et vous les lister dans la console. Mais comme vous le savez, le code `react native` est compilé en natif, donc vous perdez votre code javascript lors de la release.
Donc vos stack d’erreurs ne seront pas très lisible.

Exemple de stack d’erreur reporté par `crashlytics`.

Fatal Exception: java.lang.ClassCastException: com.facebook.react.bridge.ReadableNativeMap cannot be cast to java.lang.String
       at com.facebook.react.bridge.ReadableNativeArray.getString(ReadableNativeArray.java:102)
       at com.facebook.react.bridge.JavaMethodWrapper$5.extractArgument(JavaMethodWrapper.java:73)
       at com.facebook.react.bridge.JavaMethodWrapper$5.extractArgument(JavaMethodWrapper.java:69)
       at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:356)
       at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
       at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)

 

La méthode pour récupérer l’erreur javascript est la suivante :

crashlytics().recordError(error);

 

ErrorBoundary

L’explication étant maintenant terminée, passons à la bonne pratique. Le composant ErrorBoundary permet de rattraper les erreurs qui se produisent dans le code javascript, et de montrer une vue plus propre à l’utilisateur, au lieu de faire cracher l’application.

Comme par exemple :

error boundary

Nous allons mettre en place ce composant, et envoyer les erreurs à Crashlytics à ce moment.
Dans `component/ErrorBoundary.js` :

import crashlytics from '@react-native-firebase/crashlytics';
import { node } from 'prop-types';
import React, { Component } from 'react';
import { SafeAreaView, Text, View } from 'react-native';
import RNRestart from 'react-native-restart'

import Button from '~/components/common/Button';
import t from '~/configs/i18n';

import styles from './styles';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);

    this.state = {};
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {   
    if(errorInfo && errorInfo.componentStack) {
      crashlytics().log(errorInfo.componentStack);
    }
    crashlytics().recordError(error, error.message ? error.message : undefined);
  }

  onRestart() {
    RNRestart.Restart();
  };

  render() {
    const { hasError } = this.state;
    const { children } = this.props;

    if (hasError) {
      return (
        <SafeAreaView style={{ flex: 1 }}>
          <View style={styles.container}>
            <View style={styles.content}>
              <Text style={{ width: '100%', }}>
                500
              </Text>
              <Text style={{ fontSize: 32 }}>{t('label.error')}</Text>
              <Text style={{ marginVertical: 10, lineHeight: 23, fontWeight: '500' }}>
                {t('error.description')}
              </Text>
              <Button
                onPress={() => this.onRestart()}
              >
                {t('action.backToLogin')}
              </Button>
            </View>
          </View>
        </SafeAreaView>
      );
    }

    return children;
  }
}

ErrorBoundary.propTypes = {
  children: node,
};

ErrorBoundary.defaultProps = {
  children: null,
};

export default ErrorBoundary;

 

Puis dans `App.js`, entourez votre application avec ce composant :

return (
    <ErrorBoundary>
      <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
          {storybookActive ? <StorybookUIRoot /> : <Router />}
        </PersistGate>
      </Provider>
  </ErrorBoundary>
  );

 

Voila ! Maintenant à chaque crash, nous allons envoyer les informations js à la console `Crashlytics` de `Firebase`. Ce que j’aime bien mettre en place, c’est le rechargement de l’application lors du clic sur le bouton, grâce à la dépendance react-native-restart. C’est optionnel bien sûr.

Loggez encore plus d’information

Grâce à l’étape précédente, vous aurez la stacktrace javascript. Mais c’est toujours mieux de rajouter plus d’info (bien sûr, avec parcimonie).

Pour cela 2 méthodes :

I am text block. Click edit button to change this text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.

crashlytics().log(errorInfo.componentStack); //celle-là permet de logger une activité sur crashlytics
crashlytics().setAttribute(attribute, value); //celle-là permet de mettre un attribut

 

J’utilise la 2ème méthode pour savoir sur quelle page l’utilisateur se trouvait avant le grand crash.
Si vous utilisez @react-navigation/native :

<NavigationContainer
    theme={AppTheme}
    ref={navigationRef}
    onReady={() => {
      routeNameRef.current = navigationRef.current.getCurrentRoute().name;
      crashlytics().setAttribute("screen", routeNameRef.current);
    }}
    onStateChange={() => {
      const previousRouteName = routeNameRef.current;
      const currentRouteName = navigationRef.current.getCurrentRoute().name;

      if (previousRouteName !== currentRouteName) {
        crashlytics().setAttribute("screen", currentRouteName);
      }

      routeNameRef.current = currentRouteName;
    }}
  >

 

Conclusion

Et voila ! Ça fait pas mal d’information quand même. Maintenant j’espère que vous allez pouvoir débugger vos applications plus facilement. Ne tracker pas des informations susceptibles d’identifier vos utilisateurs (#rgpd).