C’est dans un récent tweet de @DHH, qu’à des fins de R&D chez Dexem nous avons souhaité tester la dernière bêta de Ruby On Rails qui intègre nouvelle version de Turbolinks.
Qu’est-ce que Turbolinks ?
Son nom vous dit peut-être déjà quelque chose et pour cause ce composant existe déjà dans la version 4 de Rails. Pour faire simple, l’objectif de Turbolinks est de transformer votre application Rails en une application Javascript consultable sur une seule et même page. Le client ne générant plus systématiquement tout le contenu de la page au profit du serveur qui vient transmettre les nouvelles données, remplacées dans le body courant. Ce qui n’est d’ailleurs pas sans nous rappeler AngularJS, puisque tous deux s’appuient sur l’API History HTML5…
Mais la grosse nouveauté de Turbolinks 5, c’est l’apparition de deux connecteurs qui permettent de créer des applications natives iOS et Android s’appuyant pour certains écrans sur votre application web Ruby On Rails 5 utilisant Turbolinks. D’un point de vue technique, ces deux composants prennent la forme d’une surcouche aux systèmes natifs de WebView et permettent de faire le lien entre vote applications Rails et votre application mobile. De fait, vous allez pouvoir gérer les callbacks de Turbolinks de votre application web dans votre application mobile et inversement.
Mise en pratique de Turbolinks 5 pour les produits Dexem
À titre d’exemple, chez Dexem, nos clients souhaitent pouvoir obtenir une extension de notre application web. Après avoir identifié avec eux leur besoin, il est apparut que les fonctionnalités à embarquer n’étaient pas nécessairement lourdes et complexes.
Partant de ce constat, plusieurs options s’offraient à nous :
- Partir sur un développement natif iOS et Android
- Partir sur un développement hybride (Titanium, Cordova, ionic)
- Tester Turbolinks 5 et Rails 5
Nous avons opté pour la troisième solution. Pourquoi ? Que ce soit sur Android ou sur iOS, il est relativement simple de mettre en place et déployer le système de WebView proposé par Turbolinks. En effet, en simplement quelques heures le pont entre votre application web et votre application mobile est fonctionnel et stable. Les compétences sollicitées sont en adéquation avec nos équipes internes et la vie de l’application mobile va être facilitée puisque dépendante de l’application web.
Comment je m’en suis servi pour faire mes prototypes
Notre besoin initial était de pouvoir nous connecter avec notre système de base de données afin de reproduire des vues web au format mobile. Quatres vues ont été développé :
- un formulaire
- une liste d’appels reçus
- un carnet de contact
- un onglet paramètres
Pré-requis :
- RubyGem
- Android Studio pour le développement de l’application Android
- La version 5.0.0.beta3 de Ruby On Rails
Nous avons tout d’abord généré un modèle d’application web à l’aide de la commande Rails.
rails _5.0.0.beta3_ generate dexem-webapp |
Ceci a pour effet d’activer automatiquement les dépendances nécessaires dans votre fichier Gemfile (donc l’intégration de Turbolinks 5). Si vous souhaitez le faire manuellement, consultez le lien suivant.
Nous avons ensuite défini quatre méthodes au sein d’un controller avec leur vue et route associées. Deux des méthodes appelant des données d’une base MySQL, les autres se chargeant simplement de rendre une vue HTML…
Puis nous avons rendu l’accès disponible depuis notre réseau à cette rapide web-app.
rails s <ip_locale>:<port> |
Intéressons nous maintenant à la partie mobile et regardons de plus près l’approche du connecteur Android de Turbolinks. À l’aide d’Android Studio, nous avons tout d’abord créé un nouveau projet d’application contenant une activité vide.
Assurez vous tout d’abord que le manifest de votre application Android dispose des bonnes permissions :
<uses-permission android:name= »android.permission.INTERNET » /> |
L’installation et l’intégration du connecteur Android Turbolinks se revèle être facile. Nous vous invitons à suivre la procédure indiquée sur Github. Quelques bases sont toutefois nécessaires pour comprendre le cycle d’une application mobile Android.
La suite consiste à implémenter l’interface Turbolinks Adapter, ajouter les signatures de méthodes obligatoires à notre classe ainsi que déclarer la Turbolinks View.
- public void onPageFinished()
- public void onReceivedError(int errorCode)
- public void pageInvalidated()
- public void requestFailedWithStatusCode(int statusCode)
- public void visitCompleted()
- public void visitProposedToLocationWithAction(String location, String action)
Afin de fonctionner vous devez obligatoirement créer une instance de l’objet TurbolinksSession sur la méthode onCreate de votre activité. C’est cette méthode qui va rendre la prise en charge de Turbolinks possible ainsi que le chargement du lien dans la WebView Turbolinks.
@Overrideprotected void onCreate(Bundle savedInstanceState) {
// Standard activity boilerplate here… super.onCreate(savedInstanceState); setContentView(R.layout.name_of_your_view_activity); // Assumes an instance variable is defined. Find the view you added to your // layout in step 1. turbolinksView = (TurbolinksView) findViewById(R.id.turbolinks_view); location = getIntent().getStringExtra(INTENT_URL) != null ? getIntent().getStringExtra(INTENT_URL) : « https://<ip_locale>:<port> »; TurbolinksSession.getDefault(this) .activity(this) .adapter(this) .view(turbolinksView) .visit(location); |
Que se passe-t-il dès lors qu’un lien est cliqué dans la web-app ? C’est le rôle de la méthode visitProposedToLocationWithAction
@Overridepublic void visitProposedToLocationWithAction(String location, String action) {
Intent intent = new Intent(this, MainActivity.class); intent.putExtra(INTENT_URL, location); this.startActivity(intent); } |
Cette méthode est appelée systématiquement dès que Turbolinks intercepte un lien cliqué dans votre web-app. Elle a pour but de créer une nouvelle activité et d’y attacher l’URL à visiter. Le tout étant ensuite de nouveau intercepté par notre méthode onCreate qui se charge de réaliser ce que nous avons dit auparavant.
Mais vous pouvez également vous servir de la signature de cette méthode pour réaliser des actions plus complètes telles que : ouvrir une application native, démarrer une nouvelle activité…
C’est d’ailleurs ce que nous avons réalisé chez Dexem : nous avons distingué deux types de vues, représentant deux activités différentes.
Pour conserver notre historique de navigation, il conviendra également d’implémenter dans la méthode onRestart de notre activité :
@Overrideprotected void onRestart() {
super.onRestart(); TurbolinksSession.getDefault(this) .activity(this) .adapter(this) .restoreWithCachedSnapshot(true) .view(turbolinksView) .visit(location); } |
Pas de grosses surprises ici, il s’agit de nouveau de faire appel à l’instance par défaut de TurbolinksSession, puis d’y rajouter l’appel à restoreWithCachedSnapshot(true) afin de conserver la position de l’utilisateur dans la WebView.
Voici à quoi doit ressembler votre classe pour l’implémentation dans une seule activité :
package com.dexem.callmanager;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.content.Intent; import com.basecamp.turbolinks.TurbolinksAdapter; import com.basecamp.turbolinks.TurbolinksSession; import com.basecamp.turbolinks.TurbolinksView; public class MainActivity extends AppCompatActivity implements TurbolinksAdapter { private static final String INTENT_URL = « intentUrl »; private static final String BASE_URL = « https://<ip_locale>:<port> »; private String location; private TurbolinksView turbolinksView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); turbolinksView = (TurbolinksView) findViewById(R.id.turbolinks_view); location = getIntent().getStringExtra(INTENT_URL) != null ? getIntent().getStringExtra(INTENT_URL) : BASE_URL; TurbolinksSession.getDefault(this) .activity(this) .adapter(this) .view(turbolinksView) .visit(location); } @Override protected void onRestart() { super.onRestart(); TurbolinksSession.getDefault(this) .activity(this) .adapter(this) .restoreWithCachedSnapshot(true) .view(turbolinksView) .visit(location); } @Override public void onPageFinished() { } @Override public void onReceivedError(int errorCode) { } @Override public void pageInvalidated() { } @Override public void requestFailedWithStatusCode(int statusCode) { } @Override public void visitCompleted() { } @Override public void visitProposedToLocationWithAction(String location, String action) { Intent intent = new Intent(this, MainActivity.class); intent.putExtra(INTENT_URL, location); this.startActivity(intent); } } |
Démarrer le développement maintenant ?
Si la version 5 de Turbolinks est encore un peu jeune et manque de communauté, le développement d’application mobile reposant sur Turbolinks représente toutefois une perspective intéressante. En effet, en adoptant un développement hybride reposant en grande majorité sur une application web sous Rails, le coût de maintenance et développement est nettement inférieur à des équipes spécialisées dans le développement natif. Pour ceux qui auraient encore des doutes, sachez que Basecamp 3 utilise Turbolinks 5 et Rails 5 pour son application mobile. DHH nous propose d’ailleurs une lecture intéressante à ce sujet dans un article de son blog : Signal VS. Noize.