All posts in mobile

Découverte de JetPack Compose

Bannière Jetpack Compose

Description

Jetpack Compose est un framework d’interface utilisateur développé par Google, sorti en version 1.0 en août 2021. Comme SwiftUI pour iOS, il permet de faciliter et d’accélérer la création d’interfaces graphiques pour les applications Android. C’est une approche innovante et différente de ce que proposait Android en termes de développement d’UI via les XML Views.

Avantages

Un des principaux avantages de Compose est l’approche déclarative des éléments. Grâce à la gestion de la fonction remember. Lorsqu’un élément change d’état, l’interface se met à jour automatiquement.
Par exemple, lorsque nous déclarons une variable var name by remember { mutableStateOf("Jean") }, la valeur initiale de la variable est Jean, mais lorsque le nom changera, l’interface affichera automatiquement la nouvelle valeur associée.
Les composants sont appelés Composable et peuvent être imbriqués entre eux afin de créer des composants modulables, complexes et réutilisables. Cela permet ainsi de créer un code plus structuré et plus compréhensible pour les développeurs.
De plus, Compose est plus performant grâce à sa capacité à « recomposer » les bons composants au bon moment. C’est-à-dire qu’il est capable de mettre à jour les composants nécessitant un changement d’état sans avoir à tout recréer.
Voulant optimiser et faciliter au maximum le développement d’interfaces utilisateur, Google intègre des Composable favorisant les standards de l’écosystème Android, tels que Scaffold, qui permet de gérer une vue avec une TopBar, une BottomBar, un FloatingActionButton, tout en gérant automatiquement les bonnes marges pour le contenu.

Démonstration

Prenons l’exemple d’une interface affichant une liste de prénoms pouvant être alimentée par un champ de texte.

XML

activity_main.xml

Ce fichier va définir l’interface principale de l’application. Comprenant un EditText pour la saisie d’un nouveau nom, une ImageView pour ajouter l’élément à la liste et une RecyclerView pour afficher celle-ci. Comme la liste peut contenir un nombre indéterminé d’éléments, ce composant est plus adapté et optimisé.

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">


   <EditText
       android:id="@+id/et_name"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       android:hint="Nouveau nom"
       android:inputType="text"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toStartOf="@+id/but_add"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.0"
       tools:ignore="Autofill">


   <ImageView
       android:id="@+id/but_add"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@id/et_name"
       android:layout_marginEnd="16dp"
       android:src="@android:drawable/ic_menu_add"
       app:layout_constraintBottom_toBottomOf="@+id/et_name"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="1.0"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="@+id/et_name"
       app:tint="#2196F3">


   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/rv_names"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_below="@id/but_add"
       android:layout_marginTop="16dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/et_name">


</androidx.constraintlayout.widget.ConstraintLayout>

row_item.xml

Représentant un élément de la liste, ce fichier comprendra un TextView pour afficher le nom, ainsi qu’une ImageView pour supprimer l’entrée si nécessaire.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="vertical">


   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       android:padding="8dp">


       <TextView
           android:id="@+id/tv_name"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:textSize="16sp">


       <ImageView
           android:id="@+id/but_delete"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:srcCompat="@android:drawable/ic_delete">


   <LinearLayout>


   <com.google.android.material.divider.MaterialDivider
       android:layout_width="match_parent"
       android:layout_height="1dp">


<LinearLayout>

ListAdapter.kt

Maintenant que les fichiers XML ont été créés, nous devons les associer à leurs fichiers Kotlin. ListAdapter.kt va, comme son nom l’indique, associér l’élément de la liste avec le composant XML

class ListAdapter(
   private val names: MutableList,
   private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter() {


   inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
       val tvName: TextView = view.findViewById(R.id.tv_name)
       val butDelete: ImageView = view.findViewById(R.id.but_delete)


       init {
           butDelete.setOnClickListener {
               onDeleteClick(adapterPosition)
           }
       }
   }


   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
       val view = LayoutInflater.from(parent.context).inflate(R.layout.row_item, parent, false)
       return ViewHolder(view)
   }


   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
       holder.tvName.text = names[position]
   }


   override fun getItemCount(): Int {
       return names.size
   }
}

MainActivity.kt

Maintenant que tout est en place, nous pouvons créer le fichier principal qui gérera toute la logique de la fonctionnalité. Ce fichier associera les différents composants aux variables ainsi que ListAdapter à la RecyclerView. De plus, c’est ici que nous pourrons gérer les actions des utilisateurs, telles que l’ajout et la suppression d’un prénom avec la mise à jour de l’interface.

class MainActivity : AppCompatActivity() {


   private lateinit var etName: EditText
   private lateinit var butAdd: ImageView
   private lateinit var rvNames: RecyclerView
   private lateinit var names: MutableList
   private lateinit var adapter: ListAdapter


   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       etName = findViewById(R.id.et_name)
       butAdd = findViewById(R.id.but_add)
       rvNames = findViewById(R.id.rv_names)
       names = mutableListOf("Pierre", "Paul", "Jacques")
       adapter = ListAdapter(names) { position ->
           names.removeAt(position)
           adapter.notifyItemRemoved(position)
       }
       rvNames.setLayoutManager(LinearLayoutManager(this))
       rvNames.setAdapter(adapter)
       butAdd.setOnClickListener(View.OnClickListener {
           val name = etName.getText().toString()
           if (name.isNotEmpty()) {
               names.add(name)
               adapter.notifyDataSetChanged()
               etName.setText("")
           }
       })
   }
}

Comme nous pouvons le constater dans la MainActivity.kt, l’adaptateur a besoin d’être notifié d’un changement; à la fois lorsqu’on ajoute un élément adapter.notifyDataSetChanged() et lorsqu’on supprime un élément adapter.notifyItemRemoved(position).

Compose

Avec Compose, nous aurons besoin d’un seul fichier. Nous pouvons bien évidemment séparer les composants en plusieurs fichiers pour plus de lisibilité ou d’ergonomie.
La fonction remember nous permet de suivre les changements de valeurs, tant sur la liste des noms que sur la saisie de texte.
Le composable LazyColumn est l’équivalent d’une RecyclerView. Il permet d’optimiser l’affichage d’une liste de taille indéterminée.
Enfin, le composable FirstNameRow représente une entrée dans la liste. Il n’y a pas ici besoin d’adaptateur, il suffit de déclarer le composant tel quel et de l’intégrer dans la boucle de la LazyColumn.

MainActivity.kt

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           MainView()
       }
   }
}


@Composable
fun MainView() {
   var names by remember { mutableStateOf(listOf("Pierre", "Paul", "Jacques")) }
   var entryName by remember { mutableStateOf("") }


   Column(
       modifier = Modifier.padding(16.dp),
       verticalArrangement = Arrangement.spacedBy(16.dp)
   ) {
       TextField(
           value = entryName,
           onValueChange = { entryName = it },
           label = {"Nouveau prénom"},
           modifier = Modifier.fillMaxWidth(),
           trailingIcon = {
               Icon(
                   imageVector = Icons.Default.AddCircle,
                   contentDescription = "Ajouter prénom",
                   modifier = Modifier.clickable {
                       if(entryName.isNotEmpty()) {
                           names += entryName
                           entryName = ""
                       }
                   },
                   tint = Color.Blue
               )
           }
       )


       LazyColumn(
           verticalArrangement = Arrangement.spacedBy(5.dp)
       ) {
           items(names.count()) { index ->
               FirstNameRow(firstName = names[index]) {
                   names -= names[index]
               }
           }
       }
   }
}


@Composable
fun FirstNameRow(
   firstName: String,
   onDelete: () -> Unit
) {
   Column(
       Modifier.fillMaxWidth()
   ) {
       Row(
           Modifier.fillMaxWidth(),
           verticalAlignment = Alignment.CenterVertically
       ) {
           Text(text = firstName)
          
           Spacer(modifier = Modifier.weight(1f))


           Icon(
               imageVector = Icons.Default.Close,
               contentDescription = "Supprimer prénom",
               modifier = Modifier.clickable {
                   onDelete()
               },
               tint = Color.Red
           )
       }


       Divider()
   }
}

@Composable
fun MainActivityPreview() {
   MainView()
}

Comme nous pouvons le constater ci-dessus, aucun adaptateur n’a été initié, et aucune notification envers le LazyColumn n’a besoin d’être faite. Tout se fait distinctement, simplement et rapidement.
Nous pouvons également noter qu’il existe une fonction de prévisualisation qui permet d’afficher la vue en temps réel. Nous avons aussi la possibilité d’interagir directement avec les différents composants sans qu’aucun émulateur ou appareil physique ne soit nécessaire. Cela permet d’effectuer des tests d’UI et fonctionnels beaucoup plus rapidement.

Conclusion

Tout comme SwiftUI côté iOS, Jetpack Compose modernise le développement d’applications Android en offrant une approche plus simple, plus puissante et plus concise pour la création d’interfaces utilisateur. Les développeurs bénéficient d’une productivité accrue et d’une meilleure expérience de développement. Pour ma part, je trouve cette approche de développement bien plus intéressante et innovante que le modèle XML traditionnel. De plus, lorsqu’on développe à la fois sur Android avec Kotlin/Compose et sur iOS avec Swift/SwiftUI, nous remarquons des similitudes qui facilitent et accélèrent considérablement le développement sur les deux plateformes concurrentes.

Honeywell confie à DocDoku l’évolution de son socle mobile Android

« Une expertise évidente et une très bonne communication qui ont permis à ce projet d’être un succès » : c’est en quelques mots le retour d’Honeywell Safety and Productivity Solutions sur sa collaboration avec la Team DocDoku.

Spécialisée dans la performance des process, Honeywell Safety and Productivity Solutions construit et commercialise des solutions de capture des données (RFID, lecture de codes-barres…) et de management de l’information.

La société avait choisi DocDoku pour les accompagner dans l’évolution de leur SDK (Software development Kit) mobile sous Android.

Retrouvez la success story complète dans l’espace Nos Clients.

Les enjeux de la sécurité des données – Episode 1/3

En novembre dernier, le salon Mobility for Business réunissait plus de 3 000 décideurs sur les perspectives et les enjeux de la Mobilité aujourd’hui. La sécurité des données a été un des principaux fils rouges des conférences, preuve que le sujet est plus que jamais d’actualité pour les entreprises.

Mobilité et sécurité riment-elles toujours ?

La question se pose. Le premier contre-exemple a retenir est celui du protocole WPA2 –  niveau de sécurisation le plus élevé du Wifi – qui a révélé plusieurs failles de sécurité majeures en octobre dernier. Ces failles compromettent la sécurité des réseaux personnels (box FAI par exemple) et professionnels : la récupération malveillante de données personnelles semblait alors possible.

A partir de ce constat, comment construire un réseau fiable ?

En prenant conscience que nous sommes entrés dans une nouvelle ère. De nouveaux outils deviennent indispensables pour sécuriser les comportements mobile et sortir des modèles de sécurisation utilisés pour le PC. Pour la détection du risque mobile, la logique de signature devient obsolète et doit être remplacée par une approche Machine Learning / Big Data avec l’usage de systèmes auto-apprenant de surveillance du réseau.

Technique mais pas que

L’évolution des besoins et usages des utilisateurs doit être pris en compte par la DSI.  Intégrer un Proof Of Concept pour tester les moyens d’assurer la sécurité est une bonne démarche. L’enjeux est de trouver le compromis entre un bon niveau de sécurité et une expérience Utilisateur qui reste la plus fluide possible.
Le enjeux sont donc techniques mais ne doivent pas oublier l’Humain : coordonner les comportements et faire de chacun un ambassadeur de la sécurité des données de l’entreprise.

DocDoku Training est certifié Datadock !

Depuis le 7 août 2017, DocDoku Training est en effet certifié conforme Datadock !

Qu’est-ce que Datadock pour commencer ?

Le Datadock est un référentiel unique de données qui permet aux financeurs de la formation professionnelle (OPCA, FONGECIF….) de vérifier la conformité des organismes de formation aux critères qualité définis par la Loi.

Ainsi 20 financeurs ont défini ensemble les 21 indicateurs qui permettent de mesurer le respect des 6 critères qualité imposés par la loi. Datadock est donc l’outil qui permet aux organismes de formation de prouver qu’ils répondent factuellement à ces exigences de qualité.

En tant qu’organisme de formation, nous avons donc dû prouver notre conformité au travers des 21 indicateurs pour répondre aux 6 critères qualité.

Désormais, les financeurs vont donc nous inscrire dans leur catalogue de référencement mis à la disposition des entreprises et des salariés.

Qu’est-ce que cela vous apporte concrètement (salariés et entreprises) ?

Cela vous apporte plusieurs choses concrètes :

  • un gage de crédibilité et de qualité en ce qui concerne notre organisme de formation puisque certifié conforme aux critères de qualité de la loi et des financeurs,
  • la possibilité de financement de vos formations (individuelles ou plan de formation) : en effet depuis 2017, pour être financée une formation doit être réalisée par un organisme référencé par le financeur (et donc certifié Datadock).

Alors qu’attendez-vous pour venir vous former ou former vos équipes au développement mobile, web et big data dans nos centres de formation de Toulouse et Paris ?

Développer efficacement avec Phonegap

Une multitude d’outils

En matière d’outils de debug et de développement, le choix ne manque pas. Que vous utilisiez Linux, Windows ou MacOsX vous êtes libre de choisir les outils.

Adobe, bien qu’étant spécialiste des IDE et solutions complètes de développement, ne nous fournit pas encore un outil digne du nom d’IDE. A part la ligne de commande et une petite interface permettant d’appeler ces commandes (voir l’article), nous sommes loin des solutions auxquelles a pu nous habituer Adobe.

C’est donc à nous développeurs, de trouver le moyen le plus efficace de créer des projets, de les éditer, de les tester et de les livrer. Plusieurs solutions, libres dans la plupart des cas, vont nous aider à pallier à ce manque.

Le navigateur, un des outils les plus efficaces

Que ce soit Firefox, Chrome, Safari, ou encore IE (et oui c’est encore utilisé ;-)), le navigateur reste l’outil le plus pratique pour debugguer votre application. Bien sûr vous ne pourrez pas tester les appels aux plugins, mais c’est un must pour de la mise en page, animations, et l’inspection des variables.

Oui mais alors, comment déclencher l’évènement deviceready ?

Plusieurs solutions sont possibles et sont décrites sur stackoverflow. Le plus simple est d’utiliser le code suivant :

if (navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/)) { 
    document.addEventListener(“deviceready”, onDeviceReady, false);
} else {
    onDeviceReady();
}

L’idée est de détecter que l’on se trouve sur un appareil mobile pour s’abonner à cet évènement. Dans les autres cas, cela signifie que nous sommes sur un navigateur et que nous ne sommes pas en mesure d’avoir cet évènement, nous devons donc appeler la fonction onDeviceReady.

Ripple emulator

Ripple emulator est un plugin fonctionnant avec Firefox et/ou Chrome, il permet de visualiser le rendu de votre application dans votre navigateur.

ripple

Ripple emulator permet d’émuler certains composants d’un appareil mobile, tels que l’orientation du matériel, la géolocalisation, la détection de mouvement, parfait pour simuler un cas d’utilisation réel.

C’est une solution très efficace, pour autant qu’on utilise les versions de Phonegap compatibles avec Ripple.

Phonegap developer app

Cette application qui embarque tous les plugins de base, permet d’utiliser toutes les fonctionnalités de l’appareil. Cette application va de paire avec une instance de serveur Phonegap qu’on lance depuis sa machine.

La première chose à faire est de télécharger l’application Phonegap app developer

$ phonegap serve

[phonegap] starting app server…
[phonegap] listening on 192.168.1.12:3000
[phonegap] 
[phonegap] ctrl-c to stop the server
[phonegap]

Une fois connecté, l’application est automatiquement rechargée lors de l’édition des sources. C’est un bon début pour être efficace mais il manque :

  • cruellement la possibilité d’utiliser le remote debugging,
  • aussi la possibilité d’utiliser des plugins tiers.

Pour résumer, c’est un moyen assez efficace pour tester son application, mais pas pour la debugguer.

Chrome Web Tools

Le remote debugging avec Chrome est l’un des meilleurs outils à mon sens, si ce n’est le meilleur. Pour l’activer, il faut avoir activer le mode développeur sur l’appareil, avoir déployé votre application, et connecter votre appareil en usb.

Pour accéder à la console javascript de votre application , il faut ouvrir Chrome, et accéder à chrome://inspect

inspect

Attention : pensez à cocher la case ‘Discover usb devices’.

En cliquant sur le lien ‘inspect’, une console javascript s’ouvre et permet d’inspecter directement l’application, idéal pour mettre des points d’arrêts et monitorer les performances.

Nodejs et live reload

La solution qui me plait le plus en terme de productivité reste le live reload de l’application lors de modification des sources.

L’idée est de ne pas embarquer les sources html/javascript dans l’application Phonegap mais de les servir depuis un serveur Nodejs avec Grunt ou Gulp (voir l’article)

Un seul fichier est embarqué dans l’application (index.html), et redirige vers notre serveur Nodejs.

<html>
<head>
<meta http-equiv=”refresh” content=”1; URL=http://192.168.1.12:3000”>
</head>
<body>
    Redirecting …
</body>
</html>

Seul l’ajout ou modification de plugins nécessite un redéploiement de l’application sur l’appareil.

On bénéficie alors du remote debugging et du live reload, mais surtout on teste sur du vrai matériel.

Conclusion

Pour résumer, Phonegap laisse le développeur dans l’embarras du choix concernant l’outillage et le processus de développement. La communauté Phonegap / Cordova est active ces derniers temps et laisse présager de beaux jours (belles nuits) de développement pour l’année à venir !

Dans un prochain article, nous aborderons le framework ngCordova qui marie Cordova et AngularJS.