Blog tech

RubyMotion et Android Studio

Rédigé par Jonathan François | 4 février 2016

À l’image de ce précédent article présentant l’utilisation de l’interface builder d’Xcode au sein d’une application RubyMotion, nous allons aujourd’hui nous intéresser à l’utilisation d’Android Studio.

Android Studio est l’IDE officiel pour le développement d’applications Android basé sur IntelliJ IDEA.

Les objectifs recherchés sont :

  • éviter l’édition manuelle et fastidieuse des vues XML
  • avoir à disposition une palette de widgets prêts à l’emploi
  • avoir un aperçu visuel des vues
  • faciliter la mise en page et la gestion des thèmes
  • permettre au designer d’intégrer les maquettes mobiles

Contrairement à l’interface builder d’Xcode, Android Studio ne permet pas de créer des storyboards. Peut-être bientôt…

Afin d’illustrer cet article, nous allons reprendre l’exemple de l’article traitant l’utilisation d’interface builder, soit une application simple permettant de calculer son indice de masse corporelle (IMC).

Voici les caractéristiques des outils utilisés :

Création du projet

Générons le squelette de notre application pour Android :

motion create --template=android android_studio

Passons à la configuration de notre application afin qu’elle puisse être utilisée au sein d’Android Studio :

# ./Rakefile
...
Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'android_studio'
  app.theme = '@android:style/Theme.Material'

  app.resources_dirs = ['res']
  app.sub_activities += ['DisplayResultImcActivity']
  app.api_version = '18'
end

app.theme = '@android:style/Theme.Material' : nous spécifions ici le thème Android à utiliser par défaut pour notre application. Retrouvez la liste des différents styles disponibles par défaut pour votre application Android sur la documentation officielle. Le thème par défaut d’Android est le thème Holo.

app.sub_activities += ['DisplayResultImcActivity']: dans notre exemple, nous allons volontairement utiliser deux activités afin d’aborder comment transmettre des données entre différentes activités par le biais de l’utilisation de la classe Intent d’Android.

app.resources_dirs = ['res'] : nous prenons le soin de changer le nom du dossier par défaut contenant les ressources de notre projet car Android Studio s’attend à un dossier res et non pas resources.

app.api_version = '18' : puis nous terminons en spécifiant le niveau de l’API utilisé par l’application.

Ouvrir le projet au sein d’Android Studio

Une fois Android Studio installé et lancé, nous choisissons d’ouvrir notre projet existant.

Notre projet contient donc 2 activités : MainActivityet DisplayResultImcActivity.

MainActivity est l’activité chargée par défaut lors du lancement de l’application. DisplayResultImcActivity nous permettra d’afficher le résultat du calcul de l’IMC.

Dans notre exemple l’utilisation des fragments aurait été plus pertinente mais comme expliqué auparavant ce choix va nous permettre de parler rapidement des Intent.

Créons donc les vues de nos activités :

  • res/layout/main_activity.xml
  • res/layout/result_activity.xml

Voici à quoi ressemble Android Studio lors de l’édition d’une vue XML :

  1. arborescence des fichiers de notre projet
  2. liste des composants que l’on peut glisser/déposer sur la prévisualisation du mobile
  3. définition du mode d’affichage du fichier xml. Mode text ou design
  4. prévisualisation du mobile ou éditeur classique en fonction du mode choisi
  5. liste et gestion des composants insérés dans la vue
  6. liste et gestion de tous les attributs possibles sur le composant sélectionné

Maintenant que nous avons vu comment est organisé l’IDE, nous allons ajouter nos composants. Pour cela utilisons les composants suivants que nous allons glisser/déposer dans notre vue mobile (zone 4) :

  • Layouts -> LinearLayout (horizontal) : qui permettra de créer des containers englobant les différentes parties du formulaire
  • Widgets -> Plain TextView : pour afficher du texte fixe (label etc…)
  • Text Fields -> Plain Text : correspond à un champ texte de formulaire
  • Text Fields -> Button : pour valider notre formulaire

Comme vous pouvez le voir dans la liste des composants (à droite), chaque partie de notre formulaire est englobée dans un LinearLayout. Cela facilite la mise en page de notre vue.

Prenons l’exemple de la première partie du formulaire dédiée au nom de l’utilisateur :

layout:width -> match_parent : nous définissons la largeur de notre élément en fonction de notre élément parent, ici la vue globale, soit toute la largeur de l’écran

layout:height -> match_parent : nous définissons la hauteur de notre élément en fonction de son contenu

layout:gravity -> [center_horizontal] : notre élément est centré horizontalement

Je vous laisse découvrir l’ensemble des attributs modifiables car la liste est conséquente et dépend du composant sélectionné. Chaque attribut est accompagné d’une liste prédéfinie de valeurs possibles ce qui évite de se perdre dans la documentation de l’API d’Android. Cela concerne essentiellement le design, la gestion des thèmes (même à l’échelle d’un composant) et les effets de transition.

La visualisation en temps réel de la modification d’attributs permet au designer d’ajuster les vues facilement.

Identification de nos composants

L’une des propriétés les plus importantes pour le développeur est l’ID du composant qui va permettre son identification au sein du code RubyMotion. Cette propriété est présente dans la liste des propriétés modifiables de l’interface.

Une fois enregistré nous allons pouvoir retrouver notre élément via son ID. Voici le code de notre activité principale :

class MainActivity < Android::App::Activity
  def onCreate(savedInstanceState)
    super
    setTitle('Android Studio')
    setContentView(R::Layout::Main_layout)

    imcButton = self.findViewById R::Id::ButtonCalculate
    imcButton.onClickListener = self
  end

  def onClick(view)
    name   = self.findViewById(R::Id::EditName).text.toString
    weight = self.findViewById(R::Id::EditWeight).text.toString
    height = self.findViewById(R::Id::EditHeight).text.toString
    age    = self.findViewById(R::Id::EditAge).text.toString

    intent = Android::Content::Intent.new(self, DisplayResultImcActivity)
    intent.putExtra('name', name)
    intent.putExtra('weight', weight)
    intent.putExtra('height', height)
    intent.putExtra('age', age)

    startActivity intent
  end
end
  • setTitle : label de notre activité
  • setContentView : définit la vue à utiliser pour notre activité. Ici nous utilisons bien res/layout/main_layout.xml
  • self.findViewById : permet de retrouver le composant de notre vue en spécifiant son ID que l’on a préalablement enregistré depuis Android Studio.
  • Android::Content::Intent : permet de définir de façon abstraite une opération à effectuer. Il est généralement utilisé pour réaliser la liaison entre activités. Ici nous l’utiliserons afin de réaliser la transition entre nos 2 activités en lui passant les informations de notre formulaire afin d’en afficher le résultat après traitement.
  • putExtra : permet d’encapsuler des données dans notre objet intent. Dans notre exemple nous lui passons une clé et une valeur, toutes les deux au format texte.

La documentation complète de la classe Intent vous permettra de voir toutes les méthodes accessibles.

Par exemple, nous aurions pu utiliser la méthode putExtras en passant par la construction d’un objet Android::OS::Bundle contenant nos données sérialisées.

Voyons maintenant comment récupérer les données dans notre deuxième activité. Toujours en utilisant Android Studio nous créons et mettons en place la vue dédiée à cette activité (./res/layout/result_activity.xml).

Comme vous pouvez le voir la vue est assez simple. Elle contient :

  • un titre et un label
  • plusieurs zones permettant d’afficher le résultat sous forme de description, catégorie et indice
class DisplayResultImcActivity < Android::App::Activity
  def onCreate(savedInstanceState)
    super
    setTitle('Votre IMC')
    setContentView(R::Layout::Result_layout)

    options = {
      name: intent.getStringExtra('name'),
      age: intent.getStringExtra('age'),
      weight: intent.getStringExtra('weight'),
      height: intent.getStringExtra('height')
    }

    textResult      = self.findViewById(R::Id::TextResult)
    textImc         = self.findViewById(R::Id::TextImc)
    textImcCategory = self.findViewById(R::Id::TextImcCategory)
    imc             = calcul_imc(options[:weight], options[:height])

    textResult.text      = generate_description_text(options)
    textImc.text         = imc
    textImcCategory.text = imc_category(imc)
  end
end

Notre activité de destination a donc bien accès à l’objet intent déclaré dans notre activité principale et contenant toutes les informations nécessaires.

Pour une facilité de manipulation des données au sein d’un helper, nous créons un objet JSON. Nous identifions nos composants grâce à leur ID comme précédemment puis nous en modifions le contenu avant l’affichage de la vue.

generate_description_text, calcul_imc et imc_category sont des helpers permettant le calcul et la mise en forme du résultat de l’indice de masse corporelle.

Vous pouvez retrouver le code de l’application sur Github.

Voici les deux écrans de l’application :

Conclusion

L’utilisation d’Android Studio au sein d’un projet RubyMotion (ou pas d’ailleurs) permet d’augmenter sa productivité sur la mise en place des différentes vues de l’application. Un designer qui prendrait le temps de prendre en main l’IDE serait capable de réaliser l’intégration complète de ces maquettes ou mockups. Et là c’est le développeur qui est content de plus avoir à écrire du XML à la main ou d’ajouter du code pour créer, designer et placer des éléments graphiques.

N’hésitez pas à nous faire des retours !

L’équipe Synbioz.

Libres d’être ensemble.