Hello la famille, j’espère que vous aller bien, moi j’ai plutôt la pêche 😀 !
Alors aujourd’hui, nous allons continuer avec notre superbe application de Quiz (lol), ce que nous allons faire, c’est comme le titre l’a indiqué c’est de faire en sorte d’enregistrer nos scores en utilisant une base de donnée SQLite mais en se basant sur l’utilisation de la librairie Anko qui va nous faciliter la vie, et à la fin nous allons afficher nos scores sous forme de liste en utilisant un RecyclerView
Ajout des librairies
Au niveau de notre build.gradle (Module : app), ajoutez les librairies suivantes :
1 2 3 4 |
// Pour l'utilisation de Anko avec Sqlite implementation 'org.jetbrains.anko:anko-sqlite:0.10.0' // Pour la création de notre liste avec un RecyclerView implementation 'com.android.support:recyclerview-v7:27.1.1' |
Implémenter SQLite
Pour utiliser Sqlite comme je l’ai cité plus haut, nous utiliserons Anko, pour faire mon tuto je me suis basé sur cette excellent article qui explique comment l’ajouter.
Dans notre cas vous allez suivre les étapes suivantes, j’ai commenté mon code afin d’y apporter plus de clarification.
D’abord mettons de l’ordre au niveau de notre projet.
- Créez les packages model, db, activities et adapter.
- Au niveau du package db créer les class MySQLDBHelper et QuizDB et le fichier Tables
MySQLDBHelper.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package com.technicien_superieur.thequizcapitale.db import android.content.Context import android.database.sqlite.SQLiteDatabase import com.technicien_superieur.thequizcapitale.App import org.jetbrains.anko.db.* /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ class MySqlDBHelper(ctx: Context = App.instance) : ManagedSQLiteOpenHelper(ctx, DB_NAME, null, DB_VERSION) { // Ceci représente nos constantes companion object { val DB_NAME = "quiz_app.db" val DB_VERSION = 2 val instance by lazy { MySqlDBHelper() } } override fun onCreate(db: SQLiteDatabase) { // Ici nous créeons notre table "Score", si elle existe tant mieux sinon elle est crée db.createTable(ScoreTable.NAME, true, ScoreTable.ID to INTEGER + PRIMARY_KEY, ScoreTable.SCORE to INTEGER, ScoreTable.DATE to INTEGER ) } // Mettre à jour notre base de donnée à la nouvelle version override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { db.dropTable(ScoreTable.NAME, true) onCreate(db) } } |
QuizDB.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.technicien_superieur.thequizcapitale.db import com.technicien_superieur.thequizcapitale.model.Score import org.jetbrains.anko.db.classParser import org.jetbrains.anko.db.insert import org.jetbrains.anko.db.select /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ class QuizDB(private val dbHelper: MySqlDBHelper = MySqlDBHelper.instance) { // Pour récupérer nos scores fun requestScores() = dbHelper.use { select(ScoreTable.NAME, ScoreTable.SCORE, ScoreTable.DATE) .parseList(classParser<Score>()) } // Pour engistrer un score fun saveScore(scoreQuiz: Score) = dbHelper.use { insert(ScoreTable.NAME, ScoreTable.SCORE to scoreQuiz.score, ScoreTable.DATE to scoreQuiz.date) } } |
Tables.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.technicien_superieur.thequizcapitale.db /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ // Notre table "Score" celle qu'on va utiliser object ScoreTable { val NAME = "ScoreQuiz" const val ID = "_id" const val SCORE = "score" const val DATE = "date" } |
A ce niveau là vous allez avoir des erreurs car qu’il vous manque la classe App
Au niveau du package principale créer la classe App
App.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.technicien_superieur.thequizcapitale import android.app.Application /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ class App : Application() { // On créeait une instance de App pour pouvoir l'utiliser dans toutes l'application // Exemple comme nous l'avons fait au niveau de 'MySqlDBHelper.tk' companion object { lateinit var instance: App } override fun onCreate() { super.onCreate() instance = this } } |
Bien évidemment au niveau de notre fichier AndroidManifest.xml, n’oubliez pas de rajouter la ligne suivante, sinon vous risquez d’avoir une surprise après 😀 :
1 |
android:name=".App" |
Enregistrer nos scores
Maintenant nous allons faire en sorte d’enregistrer nos scores quand le quiz se termine.
Ici nous allons modifier notre classe QuizActivity.tk qui va devenir comme ceci :
( Voir à partir de la ligne 21 et 71 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
package com.technicien_superieur.thequizcapitale.activities import android.content.Context import android.content.DialogInterface import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.app.AlertDialog import android.view.View import android.widget.Toast import com.technicien_superieur.thequizcapitale.db.MySqlDBHelper import com.technicien_superieur.thequizcapitale.R import com.technicien_superieur.thequizcapitale.db.QuizDB import com.technicien_superieur.thequizcapitale.model.Quiz import com.technicien_superieur.thequizcapitale.model.Score import kotlinx.android.synthetic.main.activity_quiz.* import org.jetbrains.anko.doAsync import java.util.* class QuizActivity : AppCompatActivity() { // On créer un objet QuizDB afin de pouvoir enregister nos score à la fin val quizDb = QuizDB() var quizs = ArrayList<Quiz>() var numberOfGoodAnswers: Int = 0 var currentQuizIndex: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_quiz) quizs.add(Quiz("Quelle est la capitale de l'Algérie ?", "Alger", "Paris", "Marseille", 1)) quizs.add(Quiz("Quelle est la capitale de la France ?", "Alger", "Paris", "Marseille", 2)) quizs.add(Quiz("Quelle est la capitale de l'Angola ?", "Alger", "Paris", "Luanda", 3)) quizs.add(Quiz("Quelle est la capitale de l'Autriche ?", "Alger", "Vienne", "Marseille", 2)) //Pour mélanger les questions Collections.shuffle(quizs); showQuestion(quizs.get(currentQuizIndex)) } fun showQuestion(quiz: Quiz) { txtQuestion.setText(quiz.question) answer1.setText(quiz.answer1) answer2.setText(quiz.answer2) answer3.setText(quiz.answer3) } fun handleAnswer(answerID: Int) { val quiz = quizs.get(currentQuizIndex) if (quiz.isCorrect(answerID)) { numberOfGoodAnswers++ Toast.makeText(this, "+1", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "+0", Toast.LENGTH_SHORT).show() } // Pour pouvoir aller à la question suivante currentQuizIndex++ if (currentQuizIndex >= quizs.size) { // Partie terminé val sharedPreferences = getSharedPreferences("com.technicien_superieur.thequizcapitale", Context.MODE_PRIVATE) sharedPreferences.edit().putInt("userScore", numberOfGoodAnswers).apply() // On registre notre score au niveau de notre base de donnée // On utilise un doAsync afin de ne pas ralentir l'interface de l'utilisateur doAsync { val quizScore = Score(numberOfGoodAnswers, Calendar.getInstance().time.time) quizDb.saveScore(scoreQuiz = quizScore) } var alert = AlertDialog.Builder(this) alert.setTitle("Partie terminé!") alert.setMessage("Tu as eu : " + numberOfGoodAnswers + " bonne(s) réponse(s)") alert.setPositiveButton("OK") { dialogInterface: DialogInterface?, i: Int -> finish() } alert.show() } else { // On continue la partie showQuestion(quizs.get(currentQuizIndex)) } } fun onClickAnwerOne(view: View) { handleAnswer(1) } fun onClickAnwerTwo(view: View) { handleAnswer(2) } fun onClickAnwerThree(view: View) { handleAnswer(3) } } |
Super maintenant nous sommes capable d’enregistrer nos scores, bon maintenant il nous reste plus qu’à les afficher à l’utilisateur.
Affichage des scores avec un RecyclerView
Maintenant nous allons créer l’activité ListScoresActivity, c’est ici que nous afficherions nos scores.
Au niveau de model créer la class Score
Score.tk
1 2 3 4 5 6 7 |
package com.technicien_superieur.thequizcapitale.model /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ data class Score(val score: Int, val date: Long) |
Au niveau du package adapter nous créerons notre classe ScoreAdapter afin de l’utiliser au niveau de notre RecyclerView
ScoreAdapter.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package com.technicien_superieur.thequizcapitale.adapter import android.content.Context import android.support.v7.widget.RecyclerView import android.text.format.DateUtils import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.technicien_superieur.thequizcapitale.R import com.technicien_superieur.thequizcapitale.model.Score import kotlinx.android.synthetic.main.score_item.view.* /** * Created by Fayçal KADDOURI on 27/10/2018. * Website : www.technicien-superieur.com */ class ScoreAdapter(val scores: List<Score>, val context: Context) : RecyclerView.Adapter<ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(context).inflate(R.layout.score_item, parent, false)) } // On renvoit le notre d'objet score qu'il y a dans notre liste override fun getItemCount(): Int { return scores.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder?.txtScore.text = "Score : ${scores.get(position).score}" // On transforme notre temps unix en temps relatif exmple "Il y a une heure...etc" val relativeTime = DateUtils.getRelativeTimeSpanString(scores.get(position).date) holder.txtDate.text = "$relativeTime" } } class ViewHolder (view: View) : RecyclerView.ViewHolder(view) { val txtScore = view.txt_score val txtDate = view.txt_date } |
Et l’activité qui va utiliser notre adapter
ListScoresActivity.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.technicien_superieur.thequizcapitale.activities import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import com.technicien_superieur.thequizcapitale.R import com.technicien_superieur.thequizcapitale.adapter.ScoreAdapter import com.technicien_superieur.thequizcapitale.db.QuizDB import kotlinx.android.synthetic.main.content_list_scores.* import org.jetbrains.anko.AnkoLogger import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread class ListScoresActivity : AppCompatActivity(), AnkoLogger { val quizDb = QuizDB() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_list_scores) score_list.layoutManager = LinearLayoutManager(this) doAsync { val list = quizDb.requestScores() // pour pouvoir afficher les scores à notre utilisateur // C'est genre l'équivalant de onPostExecute d'un AsyncTask uiThread { score_list.adapter = ScoreAdapter(list, this@ListScoresActivity) } } } } |
Et ajoutez au niveau du dossier layout les trois fichiers XML suivant :
activity_list_scores.xml
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activities.ListScoresActivity"> <include layout="@layout/content_list_scores" /> </android.support.design.widget.CoordinatorLayout> |
content_list_scores.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".activities.ListScoresActivity" tools:showIn="@layout/activity_list_scores"> <android.support.v7.widget.RecyclerView android:id="@+id/score_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.constraint.ConstraintLayout> |
score_item.xml, c’est à dire à quoi va ressembler la ligne de score
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="120dp"> <TextView android:id="@+id/txt_score" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_centerVertical="true" android:text="Score : 1" android:textSize="20sp" android:padding="20dp"/> <TextView android:id="@+id/txt_date" android:layout_alignParentEnd="true" android:layout_alignParentTop="true" android:text="Il y a 2 minutes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:padding="20dp"/> </RelativeLayout> |
Bon à ce stage là, il faut faire en sorte que quand on clique sur notre bouton “Mes Scores” cela nous mène directement à notre activity ListScoresActivity.tk
Donc on va modifier notre classe MainActivity.
MainActivity.tk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.technicien_superieur.thequizcapitale.activities import android.content.Context import android.content.Intent import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.view.View import com.technicien_superieur.thequizcapitale.R import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onResume() { super.onResume() val sharedPreferences = getSharedPreferences("com.technicien_superieur.thequizcapitale", Context.MODE_PRIVATE) val userScore = sharedPreferences.getInt("userScore", -1) if (userScore > -1) { textView.text = "Ton dernier score est de $userScore" } } fun onClickBtnPlay(view:View) { val intent = Intent(this, QuizActivity::class.java) startActivity(intent) } fun onClickBtnScores(view: View) { val intent = Intent(this, ListScoresActivity::class.java) startActivity(intent) } } |
N’oubliez pas d’ajouter la méthode “onClickBtnScores” au niveau du fichier activity_main.xml sinon le clique ne fonctionnera pas.
Voilà, normalement maintenant votre score sera enregistré au niveau de votre base de données à chaque fin de partie, et quand vous irait sur la partie “Mes scores” vous pourrez voir vos scores sous forme de liste.
Je n’ai pas voulu en faire une vidéo, car je vous avoue que je me sens encore novice côté BDD avec Kotlin et Anko, pour plus de détail je vous recommande fortement de lire cet article.
Si vous avez des questions ou quelque chose ne fonctionne pas correctement, laissez un commentaire et je vous aiderai 😀