Blog technique sur mes expériences de développeur.
5 septembre 2014
Je vous propose aujourd’hui un article dans lequel nous allons monter un projet utilisant la bibliothèque Moodstocks. Cette bibliothèque très impressionnante vous permet de mettre en place de la reconnaissance d’images dans vos applications Android, iOS ou encore Phonegap.
C’est à la partie Android que nous allons nous intéresser aujourd’hui.
Comme je vous le disais dans l’introduction de cet article, Moodstocks est une bibliothèque très impressionnante qui permet de mettre en place de la reconnaissance d’images dans des applications mobiles. Cette bibliothèque est d’autant plus intéressante et impressionnante car :
Au cours de cet article, nous allons bien évidemment manipuler la bibliothèque dans le cadre d’une application Android, mais avant ça, nous allons voir comment configurer les images à reconnaître grâce au back-end et un petit logiciel proposé par les créateurs de cette solution logicielle.
Avant toute chose, nous allons nous créer un compte sur le site de Moodstocks. Une fois votre compte validé, vous allez être invité à créer une application :
Une fois l’application créée, vous devriez arriver sur le tableau de bord :
Comme vous pouvez le voir dans la capture d’écran ci-dessus, le tableau de bord vous permet de connaître les informations relatives à votre application comme par exemple :
A tout moment, vous pouvez bien évidemment choisir de changer de plan pour un plus adapté à vos besoins !
Nous allons maintenant définir quelques images que notre application Android pourra reconnaître. Pour ça, il convient de charger sur les serveurs de Moodstocks les images.
Pour charger ces images sur les serveurs de Moodstocks, nous allons passer par un petit outil appelé Desktop uploader téléchargeable gratuitement depuis le tableau de bord de votre application :
Téléchargez puis installez la version du logiciel correspondant à votre système d’exploitation. Une fois l’installation terminée, ouvrez le logiciel :
Saisissez les codes donnés dans le tableau de bord de l’application puis glissez les images que vous souhaitez que votre application puisse reconnaître. Une fois terminé, cliquez sur synchronize :
A noter que vous pouvez personnaliser les identifiants des images en faisant un clic droit puis en choisissant Edit image ID.
Maintenant que la partie serveur est en place nous allons pouvoir entamer le développement de l’application Android. Commencez par créer une application Android classique.
Avant de commencer à manipuler Moodstocks, nous allons intégrer les bibliothèques nécessaires à son fonctionnement dans notre projet Android. Pour ça, rendez-vous sur la page downloads du site et téléchargez la version correspondant à votre IDE. Dans mon cas, il s’agit d’Eclipse.
Ouvrez l’archive et découvrez le contenu du dossier libs :
Déplacez alors ce contenu dans le dossier libs de votre projet Android (que vous soyez sous Eclipse ou sous Android Studio le principe est le même) et n’oubliez pas d’ajouter les fichier jar au Build Path :
Afin que notre application puisse fonctionner sans problème, nous allons devoir ajouter quelques informations à notre fichier AndroidManifest.xml à savoir :
En plus de ces permissions, Moodstocks nous conseille d’ajouter quelques informations relatives à l’utilisation de la caméra sur le téléphone.
Voici ce à quoi doit ressembler votre fichier AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.rolandl.blog.moodstocks"
android:versionCode="1"
android:versionName="1.0"
>
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19"
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
android:required="false"
/>
<uses-feature
android:name="android.hardware.camera.any"
android:required="false"
/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Maintenant que les bibliothèques ont été ajoutés et que notre AndroidManifest.xml est à jour, nous allons pouvoir passer à l’étape suivante à savoir la synchronisation avec le serveur.
Pour faciliter les choses, je vous propose de créer une nouvelle activité Splashscreen.java qui servira comme son nom semble l’indiquer de splaschscreen et qui s’occupera de synchroniser les informations du serveur avec l’application au démarrage de l’application.
Créez donc une activité :
public final class Splashscreen
extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
}
}
Son layout est également très simple puisqu’il s’agit simplement d’uneRelativeLayout :
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Finalement, il convient de déclarer notre Activité dans le fichier AndroidManifest.xml comme l’activité à lancer au démarrage :
<activity
android:name=".Splashscreen"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Dans la méthode onCreate
de notre Activité Splashscreen, nous allons afficher une ProgressDialog
afin de montrer aux utilisateurs que l’application travaille :
ProgressDialog.show(this, "", "Chargement", true);
Suite à l’affichage de cette ProgressDialog
,toujours dans la méthode onCreate
, nous allons lancer la synchronisation de l’application avec le serveur de Moodstocks :
Voici donc ce que vous devriez avoir :
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
ProgressDialog.show(this, "", "Chargement", true);
//TODO : écrire la synchro ici.
}
La première étape consiste à récupérer un objet Scanner
via la méthode statique get()
de la classe Scanner
:
final Scanner scanner = Scanner.get();
La deuxième étape consiste à préciser où est-ce que l’on souhaite que les données relatives à Moodstocks soient stockées. Pour cela, il convient d’utiliser la méthode statique pathFromFilesDir
de la classe Scanner
:
final String path = Scanner.pathFromFilesDir(Splashscreen.this, "scanner.db");
La troisième étape consiste à ouvrir le fameux objet Scanner
et de préciser les informations relatives à votre compte en précisant votre Api key et votre Api secret :
scanner.open(path, "key", "secret");
La quatrième étape consiste à associer un SyncListener
au Scanner
. Les différentes callbacks de l’interface seront alors appelées pendant la synchronisation, ce qui nous permettra éventuellement de faire des retours visuels à l’utilisateur :
scanner.setSyncListener(new SyncListener()
{
@Override
public void onSyncStart()
{
}
@Override
public void onSyncProgress(int arg0, int arg1)
{
}
@Override
public void onSyncFailed(MoodstocksError arg0)
{
}
@Override
public void onSyncComplete()
{
}
});
A noter que votre activité peut également implémenter l’interface !
Dans notre cas, nous allons considérer que tout va bien se passer. Nous pouvons dès maintenant compléter la méthode onSyncComplete()
en lançant l’activité suivante de notre application :
@Override
public void onSyncComplete()
{
startActivity(new Intent(Splashscreen.this, MainActivity.class));
finish();
}
Finalement, la dernière étape consiste à lancer la synchronisation :
scanner.sync();
Voici alors ce que vous devriez avoir si votre Activité implémente directement l’interface :
public final class Splashscreen
extends Activity
implements SyncListener
{
private ProgressDialog progressDialog;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
progressDialog = ProgressDialog.show(this, "", "Chargement", true);
try
{
final Scanner scanner = Scanner.get();
final String path = Scanner.pathFromFilesDir(Splashscreen.this, "scanner.db");
scanner.open(path, "lybmqf4irvivfz7jbjg9", "5ngnP8gI0ocxL4zw");
scanner.setSyncListener(this);
scanner.sync();
}
catch (MoodstocksError exception)
{
exception.printStackTrace();
}
}
@Override
public void onSyncStart()
{
Log.d("Moodstocks", "onSyncStart");
}
@Override
public void onSyncProgress(int total, int current)
{
final int percent = (int) ((float) current / (float) total * 100);
Log.d("Moodstocks", "onSyncProgress : '" + percent + "' %");
}
@Override
public void onSyncFailed(MoodstocksError exception)
{
Log.e("Moodstocks", "onSyncFailed", exception);
}
@Override
public void onSyncComplete()
{
progressDialog.dismiss();
startActivity(new Intent(Splashscreen.this, MainActivity.class));
finish();
}
}
Je vous laisse bien évidemment prendre soin de gérer correctement les différentes erreurs potentielles ;) !
Maintenant que la synchronisation avec le serveur est en place, nous allons pouvoir attaquer la reconnaissance d’image à proprement parler.
La première chose à mettre en place est le layout de l’Activité ou du Fragment dans lequel vous souhaitez mettre en place la logique du scan. Dans mon cas, il s’agit d’un Fragment. Le layout est très simple et doit uniquement comporter une SurfaceView
:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
Maintenant que le layout est en place, nous allons pouvoir nous attaquer au code-behind et pour commencer l’initialisation de la session de scan. Commencez par ajouter l’attribut suivant à votre Activité ou Fragment :
private AutoScannerSession autoScannerSession;
Nous allons maintenant instancier cet objet AutoScannerSession
dans la méthode onCreateView
du Fragment
. Le constructeur de la classe AutoScannerSession
accepte quatre paramètres :
Activity
;Scanner
;Listener
;SurfaceView
.L’Activity
, dans le cadre d’un Fragment
se récupère très facilement via la méthode getActivity()
. Le Scanner
, nous l’avons vu dans l’étape précédente se récupère également très facilement via la méthode statique get()
. La SurfaceView
est celle contenue dans notre layout. Finalement, il ne nous manque que le paramètre Listener
. Le mieux est que votre Activity
ou votre Fragment
implémente cette interface. Trois méthodes dont le nom est assez explicite doivent alors être surchargées :
@Override
public void onCameraOpenFailed(Exception exception)
{
}
@Override
public void onResult(Result result)
{
}
@Override
public void onWarning(String message)
{
}
Nous sommes donc maintenant en mesure d’instancier notre objet AutoScannerSession
:
try
{
autoScannerSession = new AutoScannerSession(getActivity(), Scanner.get(), this, (SurfaceView) rootView.findViewById(R.id.surfaceView));
}
catch (MoodstocksError exception)
{
exception.printStackTrace();
}
Le Fragment
complet quant à lui ressemble désormais à ça :
public static class PlaceholderFragment
extends Fragment
implements Listener
{
private AutoScannerSession autoScannerSession;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
try
{
autoScannerSession = new AutoScannerSession(getActivity(), Scanner.get(), this, (SurfaceView) rootView.findViewById(R.id.surfaceView));
}
catch (MoodstocksError exception)
{
exception.printStackTrace();
}
return rootView;
}
@Override
public void onCameraOpenFailed(Exception exception)
{
}
@Override
public void onResult(Result result)
{
}
@Override
public void onWarning(String message)
{
}
}
Il est possible de configurer un peu notre instance de la classe AutoScannerSession
. Par exemple le type d’images qui peuvent être reconnues :
autoScannerSession.setResultTypes(Result.Type.IMAGE | Result.Type.EAN8 | Result.Type.EAN13 | Result.Type.QRCODE | Result.Type.DATAMATRIX);
Nous allons également automatiser le lancement de la session de scan et son arrêt via les méthodes onResume
et onPause
du cycle de vie du Fragment
ou de l’Activity
:
@Override
public void onResume()
{
super.onResume();
if (autoScannerSession != null)
{
autoScannerSession.start();
}
}
@Override
public void onPause()
{
super.onPause();
if (autoScannerSession != null)
{
autoScannerSession.stop();
}
}
Nous allons donc maintenant pouvoir nous attarder sur la méthode onResult
qui est automatiquement appelée quand une image a été reconnue. Via l’objet Result
vous pouvez alors accéder à son identifiant. Par exemple, le morceau de code suivant affiche un message Toast avec l’identifiant de l’image :
@Override
public void onResult(Result result)
{
Toast.makeText(getActivity(), result.getValue(), Toast.LENGTH_LONG).show();
}
A noter qu’une fois la méthode onResult
appelé, la reconnaissance d’image n’est pas active. C’est à vous à appeler la méthode autoScannerSession.resume()
pour continuer à scanner des images !
La démonstration va être brève et se limiter à une capture d’écran :