Quixo sur mobile grâce à React-native !

Pierre Haller
Pierre HallerDecember 11, 2019
#integration#js#mobile#react

Cet article relate mon avant-dernière semaine d'intégration chez Marmelab.

La semaine précédente, j'ai pu développer une IA en Go pour pouvoir jouer seul contre l'ordinateur à Quixo. Cette semaine, l'objectif est de pouvoir jouer à Quixo depuis n'importe où grâce à une application mobile native développée avec React-native.

Pour cela je dois bien entendu développer l'application mais également une API en NodeJS.

Suivi de projet et environnement

Je suis désormais bien habitué aux outils et à la façon de travailler de Marmelab. Cette semaine sera comme les précedentes :

  • Utilisation des makefiles pour lancer le projet rapidement : make install && make start
  • Docker pour l'API
  • Trello pour le suivi de projet

Développement de l'API

Je dois désormais réaliser une API en NodeJS qui sera le backend de l'application mobile et l'application web (pour la semaine prochaine). Elle sera chargée de gérer les règles du jeu et permettra aux clients de jouer en multijoueur ou en solo.

J'ai déjà développé des API en NodeJS, ce ne sera donc pas une première. J'utiliserai un serveur express et une base de données postgres.

Lors du développement, j'ai apprécié l'utilisation des fonctions reduce, filter et map de javascript. Cela a rendu mes fonctions de vérification de victoire beaucoup plus simples qu'en PHP.

Par exemple, pour vérifier si une ligne est gagnante :

const isWinningLine = line =>
  Math.abs(line.reduce((acc, { value }) => acc + value, 0)) === line.length;

Et pour récupérer toutes les lignes gagnantes :

const getWinningLines = board => board.filter(line => isWinningLine(line));

Je n'ai pas rencontré de difficultés pour l'API. Je n'ai eu qu'à adapter le code PHP existant pour ça et ajouter des tests unitaires pour m'assurer de son bon fonctionnement.

L'application mobile en react-native

J'ai déjà développé une application mobile en react-native il y a un petit plus d'un an. Mais React et react-native ont évolué depuis. Pour React, il y a notamment l'implémentation des Hooks que je n'ai pas encore utilisé. Pour react-native il existe un nouvel outil, expo, facilitant le développement des applications.

Expo

Avec expo, il est très simple de créer une nouvelle application. Une commande suffit : expo init. Une application affichant un "hello world" est créée. Après avoir téléchargé Android Studio pour son émulateur, je n'ai plus qu'à lancer l'application : expo start. Je peux désormais lancer l'application sur l'émulateur. Je peux également lancer l'application sur mon téléphone, je n'ai qu'à installer l'application expo et flasher le QRCode présent dans mon terminal qui a été généré par expo.

Voila le resultat après un expo start :

QR Code généré par expo

J'ai été agréablement surpris par la facilité et la rapidité de mise en place du projet grâce à expo !

Développement

Les écrans

Mon application doit comporter 3 écrans :

  • Un écran d'acceuil
  • Un écran pour rejoindre une partie
  • Un écran de jeu

Pour gérer la navigation entre ces écrans, j'utilise la librairie proposée par expo react-navigation. Je ne connaissais pas cette librairie. Elle est très simple d'utilisation, il suffit de lui passer les écrans ainsi que les différentes options :

const AppNavigator = createStackNavigator(
  {
    Home: HomeScreen,
    Game: GameScreen,
    Join: JoinScreen
  },
  {
    headerMode: "none",
    navigationOptions: {
      headerVisible: false
    },
    initialRouteName: "Home"
  }
);

Une propriété navigation est injectée dans mes composants me permettant de naviguer entre les écrans en spécifiant le nom de l'écran cible :

navigation.navigate("Game")

Les hooks

Pour gérer les effets de bords, j'utilise les hooks implémentés dans react 16.8. J'utilise notamment useReducer pour stocker l'état de mon jeu et dispatcher des actions, et useEffect pour déclencher des évenements selon l'état du jeu.

J'ai trouvé les hooks très pratiques et plus naturels à utiliser que les librairies existantes comme redux ou saga. Par exemple, pour useEffect :

Il a pour but d'éxecuter un effet de bord selon le second paramètre qu'on lui passe :

  • S'il est null, la fonction sera executée après chaque rendu.
  • Si c'est un tableau vide, la fonction sera executée après le premier rendu du composant.
  • Si c'est un tableau avec des variables, la fonction sera executée si une des variables a changé.

La fonction passée en paramètre peut renvoyer une fonction qui sera appelée lors du "démontage" du composant, elle permet de "nettoyer" les effets lancés par la fonction.

Pour mon jeu, j'utilise 4 effets :

  • Le premier rafraîchit le jeu si ce n'est pas le tour du joueur
  • Le second récupère l'état du jeu après le premier rendu du composant
  • Le troisième récupère les cubes jouables après que le joueur a changé
  • Le dernier récupère l'équipe du joueur si l'id de la partie change.

Voici les 4 hooks :

useEffect(refreshGame(id, isPlaying, dispatch), null);
useEffect(fetchGame(id, dispatch), []);
useEffect(fetchMovables(id, dispatch), [currentPlayer]);
useEffect(fetchMyTeam(id, dispatch), [id]);

dispatch est créé par le hook useReducer et permet de diffuser des actions qui changeront l'état du store. Par exemple, pour refreshGame :

export const refreshGame = (id, isPlaying, dispatch) => () => {
    const refreshGameCall = async () => {
        const payload = await getExistingGame(id);
        dispatch({ type: UPDATE_GAME, payload });
    };
    if (!isPlaying) {
        const intervalId = setInterval(refreshGameCall, 1000);
        return () => clearInterval(intervalId);
    }
};

Si le joueur n'est pas en train de jouer, on rafraîchit la partie toutes les secondes. Si on quitte l'écran, on détruit l'interval.

J'ai apprécié l'utilisation des Hooks. Cela permet de ne pas avoir besoin de dépendre d'une libraire supplémentaire (comme react-redux). Et surtout, il n'y a plus besoin de faire des classes pour réaliser des opérations en fonction du cycle de vie des composants. Par exemple la fonction componentDidMount peut être remplacée par le hook :

useEffect(() => {}, [])

Résultat et conclusion

Le développement de l'application est fini, je peux désormais jouer contre l'ordinateur :

Example appli react-native

J'ai trouvé le développement en react-native simple et vraiment agréable par rapport à mes précédentes expériences en développement mobile. J'ai rencontré des difficultés lors de l'écriture des tests pour les hooks. Il existe des librairies pour ça, mais je n'ai pas réussi à m'en servir convenablement et la documentation ne m'a pas beaucoup aidé. À part ça, je n'ai pas rencontré de difficultés particulières et je pourrai ré-utiliser mes écrans la semaine prochaine pour l'application en React isomorphic avec très peu de changements !

Comme tous les projets d'intégration chez Marmelab, le code est open source et est hébergé sur GitHub. N'hésitez pas à ouvrir une PR si vous avez des idées d'amélioration.

Si vous avez raté l'article sur la création du bot en Go, vous le trouverez ici : Jouer seul à Quixo grâce à un bot en Golang

Did you like this article? Share it!