Appli Android Pour Enquête de Terrain
Appli Android Pour Enquête de Terrain
Appli Android Pour Enquête de Terrain
posté dans Android, Création Entreprise, Java, Tutoriel écrit le 1 avril 2011 par Nicolas
PARTAGER
Objectifs du tutoriel : Développer une appli Android pour réaliser une enquête de
terrain. L’appli comprend la présentation de questions (avec choix de la langue), la
saisie des réponses des personnes interrogées, le stockage dans une base de donnée,
le comptage du nombre d’enregistrements.
Avis aux entrepreneurs (ou futurs entrepreneurs), ce tuto est pour vous ! ;-)!
Voilà le but ultime du tutoriel : déployer l’application sur une tablette Android et aller
à l’assaut des centres commerciaux pour étudier un marché ;-). Voici l’appli sur ma
tablette Android :
Niveau : Moyen
Pré-requis :
Les données qui définissent le questionnaire de l’enquête sont définies dans un seul
fichier au format XML. Sans connaissance, il vous est très facile de modifier mon
fichier pour l’adapter à votre besoin.
Ensuite il vous faudra trouver un ami un peu plus à l’aise pour compiler et déployer
l’appli sur un terminal Android.
N’hésitez pas à me poser des questions !
Code source
Sommaire [Masquer]
1 Introduction
2 Spécification de notre besoin
3 Conception
4 Création d’un projet sous Eclipse
5 Layouts
6 Autres ressources
7 Code Java de l’application
o 7.1 Partie GUI (Graphical User Interface) de l’appli
7.1.1 Activité principale
7.1.2 Activité secondaire
o 7.2 Partie métier
7.2.1 Les questions
7.2.2 Le stockage des réponses
7.2.3 L’exécution des questions
o 7.3 Le fonctionnement
o 7.4 La récupération des données
8 Conclusion
Introduction
Pour ce tutoriel, je vous propose du concret !
L’appli détaillée dans ce tutoriel est très inspirée de mon appli réelle que je vais
utiliser pour réaliser une enquête de terrain pour mon étude de marché micro-
économique.
Question
Cette classe va contenir les données relatives à une question :
l’énoncé
les réponses possibles
l’orentation du radiobox (les réponses pourront s’enchaîner de manière verticale dans
le cas de textes, ou bien de manière horizontale dans le cas de notes). Un exemple des
2 possibilités :
Verticalement :
Oui
Non
Je ne sais pas
Horizontalement :
12345678
QuestionFactory
Cette classe est une factory statique (pas besoin donc de l’instancier). Elle a pour
responsabilité de construire un jeu de questions suivant la langue désirée. Elle va
aller parser le fichier de ressources « strings.xml » afin de récupérer le contenu utile.
Nous verrons plus tard comment définir ce fichier de ressources.
Pour optimiser, la construction ne devrait se faire qu’une seule fois pour chaque
langue. Dans notre pratique, l’appli ne consommera rien comme ressource de calcul
(pas de 3D, pas de gps, pas de 3G, etc…), nous reconstruirons le jeu de question à
chaque fois. À voir si le nombre de questions devient trop conséquent, il faudra
songer à pré-calculer et à stocker…
AnswersDbAdapter
Cette classe va nous permettre de manipuler une base de données au format sqlite3.
C’est le format utilisé sur android. Ce format ressemble énormément à d’autres
systèmes relationnels de base de données.
Elle fabriquera des requêtes SQL et les exécutera.
SurveyManager
C’est l’activité android (Activity) principale. Pour faire simple, c’est la fenêtre
graphique qui sera lancée au démarrage de l’appli. Elle ira faire l’inflation du calque
xml de définition de l’interface homme/machine. L’action d’inflation est simplement
le parsing et le stockage de données, rien de bien méchant…
Survey
C’est l’activité android secondaire. Elle naît sur demande de l’activité principale et
meurt quand on arrive au terme du questionnaire. Elle doit récupérer une
information importante de l’activité principale : la langue choisie ! Quand elle meurt,
elle fournira à l’activité principale le nombre de personnes déjà interrogées.
Nous avons vu les classes, mais une application android n’est pas uniquement bâtie
avec des classes Java. On peut également utiliser des ressources (images, fichiers xml,
etc).
Nous allons donc définir 2 layouts xml pour définir l’aspect graphique des 2 activités.
Nous allons y positionner des widgets.
Nous allons enfin définir le fichier strings.xml qui contiendra tout le questionnaire.
Il faut choisir un certains nombres de noms : nom du projet Eclipse nom de votre
package Java de base, nom de l’activité principale (titre notre unique fenêtre
graphique). Ensuite il nous faut choisir notre cible. Nous prendrons Android 1.5 car
nous n’aurons pas besoin d’API très spécifiques développées récemment… Le niveau
correspondant (API Level) est le niveau 3.
Voici alors l’arborescence du projet :
Layouts
Avant de coder comme des bourrins, commençons par définir notre GUI (interface
graphique). Nous allons faire simple, donc pas ultra beau… Le but est une application
qui répond à notre besoin et de minimiser le temps de développement. Enfin pour
moi c’est vital car il me reste encore quelques bricoles à terminer pour monter mon
entreprise : finir mon étude de marché macro-économique, validation du formulaire,
enquête de terrain, élaboration du business plan, aller à la chasse aux financements,
monter des dossiers, etc, etc,… bon je m’égare, ce n’est pas votre problème ;-).
Voici notre premier layout : survey_nanager.xml
ATTENTION !!! Pour m’être fait xxxxx (comprenez ce que vous voudrez!) plusieurs
fois, pas de majuscule dans le nom du layout ! Sinon le run-time plante lors de
l’inflation mais sans vous donner la moindre explication…
J’ai utilisé :
Je n’ai pas trop creusé ces mises en pages android xml, alors ce layout est largement
critiquable. J’ai même utilisé un LinearLayout pour introduire un espace vertical…
C’est moche… Mais comme mon temps est limité, je ne suis pas allé plus loin sur cet
aspect. N’hésitez pas à me donner des conseils via les commentaires du blog ! Je suis
preneur !
Pour le deuxième layout, on va faire une mise en page pour une question/réponses
seulement. On réutilisera toujours cette structure pour toutes les questions.
Voici : survey.xml
1
2 <?xml version="1.0" encoding="utf-8"?>
3
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id
4
5 <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" andro
6
7 <TextView android:text="" android:id="@+id/question" android:layout_width="wrap_
8
9 <LinearLayout android:orientation="vertical" android:layout_width="fill_parent"
10
11 <RadioGroup android:id="@+id/radioGroup1" android:layout_height="wrap_content" and
12
13 <LinearLayout android:orientation="vertical" android:layout_width="fill_parent"
14
<Button android:text="Valider" android:id="@+id/validQuestion" android:layout_widt
15
16 </LinearLayout>
17
18</ScrollView>
19
Rien à ajouter, cette étape reste assez simple à comprendre. Notez bien que chaque
balise xml peut être munie d’un nom et d’un identifiant. Ce sont ces 2 éléments qui
permettent de récupérer les données ressources à partir du Java.
Autres ressources
Bon nous n’allons pas ajouter d’images, ni d’autres fichiers. Nous allons juste ajouter
des chaînes de caractères dans le fichier strings.xml se situant dans /res/values/
Ce fichier est déjà existant, vous n’avez pas à le créer car il contient déjà le nom de
l’application.
Ici, j’ai choisi une structuration des données que vous pourrez modifier. On aurait pu
définir d’abord toutes les données françaises, puis toutes les anglaises. J’ai
personnellement préféré tout entrelacer pour ne pas oublier de modifier l’anglais si je
décide de retoucher le français. Mais si on ajoutais d’autres langues, ça deviendrait
vite fouillis…
J’ai utilisé des blocs « string » pour des phrases et des blocs « string-array » pour des
tableaux de phrases. J’ai également utilisé un « string » pour définir l’orientation du
bloc de réponse (vertical ou horizontal).
Voici : strings.xml
Je pense que ce code est assez compréhensible, je passe donc… Il vous tarde de coder,
je le sens…
Ah j’ai oublié un détail, tant que nous sommes dans le xml… Nous devons forcer
l’application en mode « landscape » pour ne pas avoir à gérer la rotation (car ce n’est
pas l’objet de ce tutoriel et ce serait superflu pour notre besoin). Pour cela rendez-
vous dans le manifest de l’appli : AndroidManifest.xml à la racine du projet.
Il faut ajouter
android:screenOrientation="landscape"
activity
que nous avons pour l’instant. Le résultat ressemble à un truc comme ceci :
<activity android:name=".SurveyManager"
android:screenOrientation="landscape" android:label="@string/app_name">
fous
Activité principale
Dans un premier temps, nous allons procéder à l’inflation du layout (c’est à dire
récupérer tous les identifiants des widgets définis dans surveyManager.xml).
L’inflation va créer une classe R qui nous permettra très facilement de récupérer des
références Java sur ces widgets. Magique :-)!
Ensuite nous allons ajouter des écouteurs afin de capter les évènements « simple
clic » sur nos 2 boutons. Pour l’instant nous ne ferons rien dans la méthode de
callback de notre écouteur. Plus tard nous lancerons une nouvelle activité android…
1 package com.codeWeblog;
2
3 import android.app.Activity;
4 import android.content.Intent;
import android.os.Bundle;
5 import android.view.View;
6 import android.view.View.OnClickListener;
7 import android.widget.Button;
8 import android.widget.TextView;
9
10 /**
11 * This is our main class. This is our main Android activity
* which be first launched.
12 *
13 * @author nvergnes
14 *
15 */
public class SurveyManager extends Activity {
16
17 /**
18 * Constant : id used for French survey activity
19 */
20 public static final int ACTIVITY_SURVEY_FR = 1;
21
22 /**
* Constant : id used for English survey activity
23 */
24 public static final int ACTIVITY_SURVEY_EN = 2;
25
26 /**
27 * Reference on a button
*/
28 Button mFrenchForm;
29
30 /**
31 * Reference on a button
32 */
33 Button mEnglishForm;
34
/**
35 * Reference on a text widget : to display the number of records
36 */
37 TextView mNbSamplesDisplay;
38
39 /**
40 * Number of samples
*/
41 int mNbSamples;
42
43 /**
44 * Called when the activity is first created.
45 */
@Override
46 public void onCreate( Bundle savedInstanceState ) {
47 super.onCreate( savedInstanceState );
48
49 // xml layout inflation
50 setContentView( R.layout.survey_manager );
51
52 // We take references on graphical widgets
mFrenchForm = ( Button )findViewById( R.id.frenchForm );
53 mEnglishForm = ( Button )findViewById( R.id.englishForm );
54 mNbSamplesDisplay = ( TextView )findViewById( R.id.nbSamples );
55
56 // We set listener on French survey launcher
57 mFrenchForm.setOnClickListener( new OnClickListener() {
@Override
58 public void onClick( View view ) {
59 // noting for now...
60 // TODO Launch a new activity
}
61 });
62
63 // We set listener on English survey launcher
64 mEnglishForm.setOnClickListener( new OnClickListener() {
65 @Override
public void onClick( View view ) {
66 // noting for now...
67 // TODO Launch a new activity
68 }
69 });
70 }
}
71
72
73
74
75
76
77
78
79
80
81
82
83
Lançons notre appli avec l’émulateur AVD… Super, voilà notre première activité!
Bon pour l’instant c’est un peu en bois tout de même… Rien ne se passe quand on
clique sur les boutons.
Activité secondaire
Avant de passer au code « métier », c’est à dire l’enregistrement des réponses, nous
allons terminer toute la partie GUI.
Passons donc à notre activité secondaire dont nous avons déjà regardé le
layout survey.xml.
package com.codeWeblog;
1
2 import android.app.Activity;
3 import android.os.Bundle;
4 import android.widget.Button;
5 import android.widget.RadioGroup;
import android.widget.TextView;
6
7
/**
8 * This is our secondary activity, called by SurveyManager activity. Its responsi
9 * is to ask all questions, and store the result, at last, in a database. Then, t
10 * activity is focused again.
11 *
* @author nvergnes
12 *
13 */
14 public class Survey extends Activity implements RadioGroup.OnCheckedChangeListener
15
16 /**
17 * Constant : the number of questions in the survey
*/
18 public static final int NUMBER_OF_QUESTIONS = 3;
19
20 /**
21 * All the answers of one person
22 */
int[] mAnswers = new int[ NUMBER_OF_QUESTIONS ];
23
24 /**
25 * Button to valid questions
26 */
27 Button mValidQuestion;
28
29 /**
* Possibles answers for each question
30 */
31 String [] mItemsQuestion;
32
33 /**
* Current question
34 */
35 int mIndexCurrentQuestion = 1;
36
37 /**
38 * Number of samples
*/
39 int mNbSamples;
40
41 /**
42 * RadioGroup
43 */
44 RadioGroup mRadioGroup;
45
/**
46 * The text of the question
47 */
48 TextView mQuestion;
49
50 /**
* Id of the the checked radio
51 */
52 int mCheckedIndex = -1;
53
54 /**
55 * Language used in survey
56 */
int mLang;
57
58 /**
59 * Called when the activity is first created.
60 */
61 @Override
62 public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
63
64 // Xml layout inflation
65 setContentView( R.layout.survey );
66
67 // We get a reference on the radio-group widget
68 mRadioGroup = ( RadioGroup )findViewById( R.id.radioGroup1 );
mRadioGroup.setOnCheckedChangeListener( this );
69
70 // We get a reference on the text-view widget
71 mQuestion = ( TextView )findViewById( R.id.question );
72
73 }
74
75 /**
76 * This method is called as soon as a radio button is clicked
*/
77 @Override
78 public void onCheckedChanged( RadioGroup radioGroup, int checkedId ) {
79 // We remember the id of the checked radio
80 mCheckedIndex = -1;
81
82 // We check all radio button value
83 for ( int i = 0; i < radioGroup.getChildCount(); i++ ) {
if ( radioGroup.getChildAt( i ).getId() == checkedId ) {
84 mCheckedIndex = i;
85 break;
86 }
87 }
}
88 }
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
Reste ensuite à déclarer cette nouvelle activité au sein du manifest. Le bloc qui suit
doit être ajouté dans le bloc xml de l’application :
Ok. Maintenant, nous devons lancer cette activité à partir de la première que nous
avons créée.
Nous allons donc devoir lancer une intention depuis notre première activité. Nous
n’allons pas laisser le choix au système android de chercher avec quel appli il pourrait
répondre à cette intention. À la place, nous allons tout simplement utiliser la
deuxième activité pour répondre à cette intention.
permet de placer une donnée dans l’activité, dans un bundle. Ici il s’agit d’un code
que j’ai défini. Je me suis défini 2 constantes une pour les questions françaises, une
pour les questions anglaises.
permet de lancer la nouvelle activité à partir de notre intention. Ici on doit passer un
entier pour coder cet appel à la nouvelle activité. J’ai réutilisé
ACTIVITY_SURVEY_FR mais c’est purement arbitraire, j’aurais pu passer n’importe
quel entier…
Pour l’écouteur sur l’autre bouton, c’est exactement le même code, je ne m’étend donc
pas.
1 /**
2 * Called when the child activity is closed, we must give focus on
3 * the initial activity : this one!
*/
4 @Override
5 protected void onActivityResult( int requestCode, int resultCode, Intent intent )
6 super.onActivityResult( requestCode, resultCode, intent );
7
8 // We get the bundle from the Survey activity
9 Bundle extras = intent.getExtras();
10
switch( requestCode ) {
11 case ACTIVITY_SURVEY_FR:
12 case ACTIVITY_SURVEY_EN:
13 mNbSamples = extras.getInt( "nbSamples" );
14 mNbSamplesDisplay.setText( Integer.toString( mNbSamples ) );
break;
15
16
default:
17 // We do nothing
18 break;
19 }
20 }
21
22
23
Cette méthode récupère le bundle crée par l’activité secondaire (ce bundle ayant été
encapsulé dans l’intention de « retour »). Ensuite elle analyse le code de retour. Ce
code correspond au code passé en argument dans :
On est OK pour cette activité. Allons donc dans l’activité secondaire : Survey.java
1
2
/**
3 * Called when the activity is first created.
4 */
5 @Override
6 public void onCreate( Bundle savedInstanceState ) {
7 super.onCreate( savedInstanceState );
8
// We get the intent to know if the survey is in French or English
9 Bundle extras = getIntent().getExtras();
10 if ( extras != null ) {
11 mLang = extras.getInt( "lang" );
12 }
13
// Xml layout inflation
14 setContentView( R.layout.survey );
15
16 // We get a reference on the radio-group widget
17 mRadioGroup = ( RadioGroup )findViewById( R.id.radioGroup1 );
18 mRadioGroup.setOnCheckedChangeListener( this );
19
20 // We get a reference on the text-view widget
mQuestion = ( TextView )findViewById( R.id.question );
21
22 }
23
24
Partie métier
Les questions
Les questions vont être construites avec une fabrique (design pattern factory), ceci à
partir du fichier de ressource strings.xml que nous avons défini.
1 package com.codeWeblog;
2
3 import android.widget.RadioGroup;
4
5 /**
* This class contains a question :
6 * - text (subject)
7 * - possibles answers
8 * - orientation (graphical widget)
9 *
* @author nvergnes
10 *
11 */
12 public class Question {
13 /**
14 * Question formulation
*/
15 String mText;
16
17 /**
18 * Possibles answers for this question
19 */
String [] mPossibleAnswers;
20
21 /**
22 * Orientation of the list which contains all possible answers
23 */
24 int mOrientation = RadioGroup.HORIZONTAL;
25
26 /**
* Setter
27 * @param text
28 */
29 void setText( String text ) {
30 mText = text;
}
31
32
/**
33 * Setter
34 * @param possibleAnswers
35 */
36 void setPossibleAnswers( String [] possibleAnswers ) {
mPossibleAnswers = possibleAnswers;
37 }
38
39 /**
40 * Setter
41 * @param orientation
42 */
43 void setOrientation( int orientation ) {
mOrientation = orientation;
44 }
45
46 /**
47 * Getter
48 * @return Orientation
*/
49 int getOrientation() {
50 return mOrientation;
51 }
52
53 /**
54 * Getter
* @return Text
55 */
56 String getText() {
57 return mText;
58 }
59
/**
60 * Getter
61 * @return Possible answers
62 */
63 String [] getPossibleAnswers() {
64 return mPossibleAnswers;
}
65
66 }
67
68
69
70
71
72
73
74
75
76
77
78
1 package com.codeWeblog;
2
3 import java.util.ArrayList;
4
5 import android.content.Context;
import android.widget.RadioGroup;
6
7 public class QuestionFactory {
8 /**
9 * Factory : This static method creates all the questions/responses from the xml
10 *
11 * @param context The context to access the resources
12 * @param numberOfQuestions Number of questions
* @return The set of questions
13 */
14 public static ArrayList<Question> createQuestions( Context context, int numberOfQue
15
16 // Construction of a set of questions
17 ArrayList<Question> questions = new ArrayList<Question>( numberOfQuestions )
18
// Parameter chosen with the language parameter
19 String question = new String( "" );
20 String answer = new String( "" );
21
22 // We adapt our construction with the language parameter
23 switch ( language ) {
24 case SurveyManager.ACTIVITY_SURVEY_FR:
question += "fr_question";
25 answer += "fr_answer";
26 break;
27
28 case SurveyManager.ACTIVITY_SURVEY_EN:
29 question += "en_question";
answer += "en_answer";
30 break;
31
32 default:
33 // impossible case
34 break;
35 }
36
// We parse the xml resources for each questions
37 for ( int i = 0; i < numberOfQuestions; i++ ) {
38
39 // To store a temp id
40 int id;
41
42 // The new question build by the factory
43 Question currentQuestion = new Question();
44
// We build an integer id from the name of the resource
45 id = context.getResources().getIdentifier( question + Integer.toString( i
46 // We get the resource in the xml file
47 currentQuestion.setText( context.getResources().getString( id ) );
48
49 // We build an integer id from the name of the resource
id = context.getResources().getIdentifier( answer + Integer.toString( i +
50 currentQuestion.setPossibleAnswers( context.getResources().getStringArray
51
52 // We build an integer id from the name of the resource
53 id = context.getResources().getIdentifier( "orientation" + Integer.toStrin
54 // We get the resource in the xml file
55 String orientation = context.getResources().getString( id );
56
if ( orientation.equalsIgnoreCase( "vertical" ) ) {
57 currentQuestion.setOrientation( RadioGroup.VERTICAL );
58 }
59 else if ( orientation.equalsIgnoreCase( "horizontal" ) ) {
60 currentQuestion.setOrientation( RadioGroup.HORIZONTAL );
}
61 else {
62 // impossible case
throw new Exception( "Mauvaise saisie dans le fichier string.xml : un
63 }
64
65 // We push a new question in the list
66 questions.add( currentQuestion );
67 }
68
// We return the result
69 return questions;
70 }
71 }
72
73
74
75
76
77
78
79
80
81
82
83
Ensuite, ayant cet id, on peut aller chercher la ressource. Attention au type de la
ressource. Il existe lesstring et les string-array. Les retours ne sont pas les mêmes.
Dans le premier cas, on récupère un String alors que dans le second cas, on récupère
un String[].
Enfin on injecte toutes ces données dans des objets Question que l’on agrège dans un
ArrayList. On a alors notre jeu de questions contenant tout : questions, réponses,
orientation du widget des réponses,…
Je ne vais pas trop m’attarder sur ce code car j’ai réutilisé les principes du tutoriel
officiel de google Notepad
: http://developer.android.com/resources/tutorials/notepad/index.html. Ce tutorial
est vraiment excellent et le code source est fourni ICI.
package com.codeWeblog;
1
2 import android.content.Context;
3 import android.database.Cursor;
4 import android.database.sqlite.SQLiteDatabase;
5 import android.database.sqlite.SQLiteException;
6 import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
7
8 /**
9 * This class
10 *
11 * @author nvergnes
*
12 */
13 public class AnswersDbAdapter {
14
15 public static final int DATABASE_VERSION = 2;
16 public static final String DATABASE_NAME = "dataBase";
17 public static final String TABLE_NAME = "answers";
18
/**
19 * Sql query to create a table in the database
20 */
21 private String mSqlCreateTable;
22
23 /**
* Sql query to to add a record in the table of the database
24
*/
25 private String mSqlAddAnswer;
26
27 /**
28 * Reference on database
29 */
private SQLiteDatabase mDataBase;
30
31 /**
32 * Context
33 */
34 private Context mContext;
35
36 /**
* Number of field, equals to the number of questions
37 */
38 private int mNbFields;
39
40 /**
* Constructor
41 *
42 * @param context
43 * @param nbFields
44 */
public AnswersDbAdapter( Context context, int nbFields ) {
45 mContext = context;
46 mNbFields = nbFields;
47
48 // We create the main queries used to create the table and add records
49 buildAddRecordQuery();
50 buildCreateNewTableQuery();
}
51
52 /**
53 * Open the database
54 * @throws SQLiteException
55 */
public void open() throws SQLiteException {
56 mDataBase = ( new DatabaseHelper( mContext, DATABASE_NAME, null, DATABASE_VER
57 }
58
59 /**
60 * Close the database
61 */
public void close() {
62 mDataBase.close();
63 }
64
65 /**
66 * Construct a sql query in order to add a new record
67 * in the database
*/
68 public void buildAddRecordQuery() {
69
70 mSqlAddAnswer = new String( "INSERT INTO " );
71
72 // Beginning of the structure
73 mSqlAddAnswer += TABLE_NAME + " (";
74
// All other fields
75 for ( int i = 0; i < ( mNbFields - 1 ); i++ ) {
76 mSqlAddAnswer += "answer" + Integer.toString( i + 1 ) + "Code" + ", ";
77 }
78 // The end of the structure
79 mSqlAddAnswer += "answer" + Integer.toString( mNbFields ) + "Code" + ") VALUE
80
}
81
82 /**
83 * Construct a sql query in order to create our table
84 * in the database
85 */
public void buildCreateNewTableQuery() {
86
87 mSqlCreateTable = new String( "CREATE TABLE " );
88
89 // Beginning of the structure
90 mSqlCreateTable += TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT,
91
// All other fields
92 for ( int i = 0; i < ( mNbFields - 1 ); i++ ) {
93 mSqlCreateTable += "answer" + Integer.toString( i + 1 ) + "Code" + " INTE
94 }
95 // The end of the structure
mSqlCreateTable += "answer" + Integer.toString( mNbFields ) + "Code" + " INTE
96 }
97
98 /**
99 * Add a new record in the database : it's a new set of answers.
100 *
101 * @param answers
*/
102 public void createAnswer( int [] answers ) {
103 // sql query initialization
104 String query = new String( mSqlAddAnswer + "( '" );
105
106 // We create the rest of the sql query
for ( int i = 0; i < answers.length-1; i++ ) {
107 query += Integer.toString( answers[ i ] ) + "' , '";
108 }
109 query += Integer.toString( answers[ answers.length - 1 ] ) + "' )";
110
111 // We execute the sql query
112 mDataBase.execSQL( query );
}
113
114 /**
115 * To get the id of the last record in database
116 *
117 * @return The id of the last record in database (it equals to the number of sa
118 */
public int getLastId() {
119 // sql query
120 String query = new String( "select count(*) from answers" );
121
122 // We execute the sql query
123 Cursor result = mDataBase.rawQuery( query, null );
124
// We set the cursor at the beginning
125 result.moveToFirst();
126
127 // We return the result : it's the number of samples
128 return result.getInt( 0 );
129 }
130
131 /**
* Inner class useful to create a new table
132 *
133 * @author nvergnes
134 *
135 */
private class DatabaseHelper extends SQLiteOpenHelper {
136
137 public DatabaseHelper( Context context, String name, CursorFactory factory, i
138 super( context, name, factory, version );
139 }
140
@Override
141 public void onCreate( SQLiteDatabase db ) {
142 db.execSQL( mSqlCreateTable );
143 }
144
145 @Override
public void onUpgrade( SQLiteDatabase arg0, int arg1, int arg2 ) {
146 // Nothing, is done here
147 }
148 }
149
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
En principe mon code est correctement documenté. Si vous ne comprenez pas tout,
posez moi des questions dans les commentaires du billet.
1 package com.codeWeblog;
2
3 import java.util.ArrayList;
4
5 import android.app.Activity;
import android.content.Context;
6 import android.content.Intent;
7 import android.database.sqlite.SQLiteException;
8 import android.os.Bundle;
9 import android.view.Gravity;
10 import android.view.View;
import android.view.View.OnClickListener;
11 import android.widget.Button;
12 import android.widget.RadioButton;
13 import android.widget.RadioGroup;
14 import android.widget.TextView;
import android.widget.Toast;
15
16 /**
17 * This is our secondary activity, called by SurveyManager activity. Its responsi
18 * is to ask all questions, and store the result, at last, in a database. Then, t
19 * activity is focused again.
20 *
* @author nvergnes
21 *
22 */
23 public class Survey extends Activity implements RadioGroup.OnCheckedChangeListener
24
25 /**
* Constant : the number of questions in the survey
26
*/
27 public static final int NUMBER_OF_QUESTIONS = 3;
28
29 /**
30 * All the answers of one person
31 */
int[] mAnswers = new int[ NUMBER_OF_QUESTIONS ];
32
33 /**
34 * Button to valid questions
35 */
36 Button mValidQuestion;
37
38 /**
* Possibles answers for each question
39 */
40 String [] mItemsQuestion;
41
42 /**
43 * Current question
44 */
45 int mIndexCurrentQuestion = 1;
46
/**
47 * Database to store results
48 */
49 AnswersDbAdapter mDataBase;
50
51 /**
* Number of samples
52 */
53 int mNbSamples;
54
55 /**
56 * RadioGroup
57 */
RadioGroup mRadioGroup;
58
59 /**
60 * Set of questions (construction with a specific factory)
61 */
62 ArrayList<Question> mQuestions;
63
/**
64 * The text of the question
65 */
66 TextView mQuestion;
67
68 /**
69 * Id of the the checked radio
*/
70 int mCheckedIndex = -1;
71
72 /**
73 * Language used in survey
74 */
75 int mLang;
76
/**
77 * Called when the activity is first created.
78 */
79 @Override
80 public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
81
82 // We get the intent to know if the survey is in French or English
83 Bundle extras = getIntent().getExtras();
84 if ( extras != null ) {
85 mLang = extras.getInt( "lang" );
86 }
87
// Xml layout inflation
88 setContentView( R.layout.survey );
89
90 // We get a reference on the radio-group widget
91 mRadioGroup = ( RadioGroup )findViewById( R.id.radioGroup1 );
92 mRadioGroup.setOnCheckedChangeListener( this );
93
// We get a reference on the text-view widget
94 mQuestion = ( TextView )findViewById( R.id.question );
95
96 try {
// We construct the set of questions
97 mQuestions = QuestionFactory.createQuestions( this, NUMBER_OF_QUESTIO
98
99 // We create the database access
100 mDataBase = new AnswersDbAdapter( (Context)this, NUMBER_OF_QUESTIONS )
101
102 // We get a reference on the validation widget
mValidQuestion = ( Button )findViewById( R.id.validQuestion );
103 mValidQuestion.setOnClickListener( new OnClickListener() {
104 @Override
105 public void onClick( View view ) {
106
107 // We create, or open the data base
108 mDataBase.open();
109
// We test if the list is empty
110 if ( mCheckedIndex == -1 ) {
111 // We show a toast to say : no answer !
112 Toast.makeText( view.getContext(), "Aucune réponse n\'a été
113 }
// In this case, the list is not empty
114 else {
115 // We show a toast to say : ok, question validated
116 Toast.makeText( view.getContext(), "Question " + Integer.toS
117 Toast.LENGTH_SHORT ).show();
118
119 // We memorize the current answer
mAnswers[ mIndexCurrentQuestion - 1 ] = mCheckedIndex;
120
121 // We go to the next question
122 mIndexCurrentQuestion++;
123
124 // If there is other questions to deal with
125 if ( mIndexCurrentQuestion <= NUMBER_OF_QUESTIONS ) {
126 mQuestion.setText("");
executeQuestion( mIndexCurrentQuestion );
127 }
128
129 // We test if the series of questions are ended
130 if ( mIndexCurrentQuestion == NUMBER_OF_QUESTIONS + 1 ) {
131 // We store all answer in database
mDataBase.createAnswer( mAnswers );
132
133 // We count the number of samples
134 mNbSamples = mDataBase.getLastId();
135
136 // We close the database at the end
137 mDataBase.close();
138
139 // Display with a toast
Toast.makeText( view.getContext(), "Fin du questionnaire
140
141 // We terminate this activity, to go back to the mana
142 // We define a bundle to store the result to send to
143 Bundle bundle = new Bundle();
144
145 // We set the number of samples in the bundle
146 bundle.putInt( "nbSamples", mNbSamples );
147
// We create a new Intent to go back to the first activi
148 Intent intent = new Intent();
149 intent.putExtras( bundle );
150
151 // We set the return status OK
152 setResult( RESULT_OK, intent );
finish();
153 }
154 }
155 }
156
157 });
158
159 // Execute the first question
executeQuestion( mIndexCurrentQuestion );
160
161 }
162 // If there is an exception, for example the database is not usable...
163 catch ( SQLiteException e ) {
164 Toast.makeText( this, "Impossible d'ouvrir la base de donnees", Toast.
}
165 catch ( Exception e ) {
166 Toast.makeText( this, "Problème... L'erreur vient probablement d'un fich
167 }
168 }
169
170 /**
* This method execute one question : display, parameterization,...
171 *
172 * @param indexQuestion Index of the question to execute
173 */
174 public void executeQuestion( int indexQuestion ) {
175 // We initialize again this attribute before each question
mCheckedIndex = -1;
176
177 // We destroy all the radio-group children
178 mRadioGroup.removeViews( 0, mRadioGroup.getChildCount() );
179
180 // We clear old choice
181 mRadioGroup.clearCheck();
182
// Current question
183 Question currentQuestion = mQuestions.get( indexQuestion - 1 );
184
185 // We set a correct display
186 mRadioGroup.setOrientation( currentQuestion.getOrientation() );
187 mRadioGroup.setHorizontalGravity( Gravity.LEFT );
188
189 // We set the text in the widget
mQuestion.setText( currentQuestion.getText() );
190
191 // We get all possible answers
192 mItemsQuestion = currentQuestion.getPossibleAnswers();
193
194 // We create some radio-button dynamically, and set the correct text
195 for ( int i = 0; i < mItemsQuestion.length; i++ ) {
RadioButton radioButton = new RadioButton( this );
196 // We set the text in the widget
197 radioButton.setText( mItemsQuestion[ i ] );
198 mRadioGroup.addView( radioButton );
199 }
}
200
201 /**
202 * This method is called as soon as a radio button is clicked
203 */
204 @Override
205 public void onCheckedChanged( RadioGroup radioGroup, int checkedId ) {
// We remember the id of the checked radio
206 mCheckedIndex = -1;
207
208 // We check all radio button value
209 for ( int i = 0; i < radioGroup.getChildCount(); i++ ) {
210 if ( radioGroup.getChildAt( i ).getId() == checkedId ) {
mCheckedIndex = i;
211 break;
212 }
213 }
214 }
215 }
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
Quelques explications…
mDataBase.open();
On exécute la question, c’est à dire on met à jour l’affichage et on réinitialise les cases
à cocher.
On mémorise toutes les réponses dans un tableau d’entiers (
mAnswers
Quand on est à la dernière question on exporte toutes les valeurs du tableau des
réponses
mAnswers
Le fonctionnement
Ok, c’est bien joli de saisir des réponses mais il faut aussi pouvoir tout récupérer pour
dépouiller ces données! Il suffit de récupérer la base dans l’emplacement suivant sur
votre terminal : data/data/nomPackageJava/databases/nomBaseDonnees
Pour ce faite, vous disposer d’un outil intégré dans le SDK Android de google. Cet
outil, c’est ADB (Android Debug Bridge). Eclipse s’appuie énormément sur ADB
même si pour nous tout est transparent. Quand vous allez débuguer votre appli,
n’oubliez pas que vous êtes sur votre PC et l’appli sur un terminal distant (que vous
soyez sur l’émulateur ou sur un terminal réel, c’est pareil dans les 2 cas).
Lancer une invite de commande, et placez vous dans le sdk. Ensuite vous rentrez dans
les répertoires suivants : ./android-sdk-linux_x86/platform-tools
adb devices
On voit dans mon exemple que seulement un émulateur est connecté. On pourrait
lancer plusieurs instances d’émulateurs avec plusieurs versions d’Android par
exemple.
Pour récupérer des données sur l’émulateur (n’oubliez pas, on cherche à récupérer les
données ;-)), on utilise la commande :
J’ai utilisé des variables d’environnement afin de vous donner une commande
générique. Vous n’avez plus qu’à remplacer les variables par des noms de votre choix.
Par exemple dans le tutoriel, on a utilisé PACKAGE=com.codeWeblog
Édition de la base sqlite3 (j’utilise SQLite Database Browser qui est le soft le plus
simple du monde) :
Il suffit de l’exporter au format csv pour pouvoir dépouiller avec un tableur. Pas mal
non?
Conclusion
Pour conclure, dans ce tutoriel, nous aurons balayé pas mal de notions qui pourront
vous être utile dans un bon nombre d’autres applications. Ces notions sont
principalement : les intentions, les activités, les ressources, les bases sqlite3,… Nous
avons codé une appli de A à Z!
Cette application peut directement servir de base pour la réadapter suivant ces
besoins pour réaliser une enquête de terrain. On peut imaginer les modifications et
améliorations suivantes :
Bon tuto