@@ -91,6 +91,86 @@ plugins {
91
91
```
92
92
#### Create the native SQL driver factory and use it for creating the DB with ` actual ` /` expect ` kotlin keywords
93
93
94
+ ``` kotlin Platform.jvm.kt (desktopMain)
95
+ actual suspend fun provideDbDriver (
96
+ schema : SqlSchema <QueryResult .AsyncValue <Unit >>
97
+ ): SqlDriver {
98
+ return JdbcSqliteDriver (" jdbc:sqlite:quiz.db" , Properties ())
99
+ .also { schema.create(it).await() }
100
+
101
+ }
102
+ ```
103
+
104
+
105
+ ``` kotlin Platform.ios.kt (iosMain)
106
+ actual suspend fun provideDbDriver (
107
+ schema : SqlSchema <QueryResult .AsyncValue <Unit >>
108
+ ): SqlDriver {
109
+ return NativeSqliteDriver (schema.synchronous(), " quiz.db" )
110
+ }
111
+ ```
112
+
113
+ ``` kotlin Platform.android.kt (androidMain)
114
+ actual suspend fun provideDbDriver (
115
+ schema : SqlSchema <QueryResult .AsyncValue <Unit >>
116
+ ): SqlDriver {
117
+ return AndroidSqliteDriver (schema.synchronous(), QuizApp .context(), " quiz.db" )
118
+ }
119
+ ```
120
+
121
+
122
+ ``` kotlin Platform.js.kt (wasmjsMain)
123
+ actual suspend fun provideDbDriver (schema : SqlSchema <QueryResult .AsyncValue <Unit >>): SqlDriver {
124
+ return WebWorkerDriver (
125
+ jsWorker()
126
+ )
127
+ }
128
+ fun jsWorker (): Worker =
129
+ js(""" new Worker(new URL("./sqljs.worker.js", import.meta.url))""" )
130
+ ```
131
+
132
+ for the WebWorker you need to create a webpack.config.d/config.js file in the root of your project and a webpack.config.d/sqljs-config.js for the web worker to be able to use the sql wasm library.
133
+
134
+ ``` js webpack.config.d/config.js
135
+ const TerserPlugin = require (" terser-webpack-plugin" );
136
+
137
+ config .optimization = config .optimization || {};
138
+ config .optimization .minimize = true ;
139
+ config .optimization .minimizer = [
140
+ new TerserPlugin ({
141
+ terserOptions: {
142
+ mangle: true , // Note: By default, mangle is set to true.
143
+ compress: false , // Disable the transformations that reduce the code size.
144
+ output: {
145
+ beautify: false ,
146
+ },
147
+ },
148
+ }),
149
+ ];
150
+ ```
151
+
152
+ ``` js webpack.config.d/sqljs-config.js
153
+ // {project}/webpack.config.d/sqljs.js
154
+ config .resolve = {
155
+ fallback: {
156
+ fs: false ,
157
+ path: false ,
158
+ crypto: false ,
159
+ }
160
+ };
161
+
162
+ const CopyWebpackPlugin = require (' copy-webpack-plugin' );
163
+ config .plugins .push (
164
+ new CopyWebpackPlugin ({
165
+ patterns: [
166
+ ' ../../node_modules/sql.js/dist/sql-wasm.wasm'
167
+ ]
168
+ })
169
+ );
170
+
171
+ ```
172
+
173
+
94
174
#### Read carefully the modelisation UML below
95
175
96
176
![ diagram SQL ] ( ../assets/images/diagramme_sql.png )
@@ -107,7 +187,6 @@ Your repository handle the following cases :
107
187
* if there is network and db data are younger than 5 min : return on the flow the db data
108
188
* if there is network and db data are older than 5 min : retourn on the flow the network data and reset db data
109
189
110
-
111
190
## 🎯 Solutions
112
191
113
192
::: details QuizDatabase.sq (ressources of commonMain)*
@@ -125,7 +204,6 @@ CREATE TABLE questions (
125
204
correctAnswerId INTEGER NOT NULL
126
205
);
127
206
128
-
129
207
CREATE TABLE answers (
130
208
id INTEGER NOT NULL ,
131
209
label TEXT NOT NULL ,
@@ -137,8 +215,6 @@ CREATE TABLE questions (
137
215
ON DELETE CASCADE
138
216
);
139
217
140
-
141
-
142
218
selectUpdateTimestamp:
143
219
SELECT *
144
220
FROM update_time;
@@ -173,133 +249,95 @@ CREATE TABLE questions (
173
249
```
174
250
:::
175
251
176
- ::: details network/QuizDB.kt (commonMain)
252
+ ::: details data/datasources (commonMain)
177
253
``` kotlin
178
- package network
179
-
180
-
181
- import app.cash.sqldelight.async.coroutines.awaitAsList
182
- import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
183
- import app.cash.sqldelight.db.SqlDriver
184
- import com.myapplication.common.cache.Database
185
- import kotlinx.coroutines.CoroutineScope
186
- import network.data.Answer
187
- import network.data.Question
188
-
189
- class QuizDbDataSource (private val sqlDriver : SqlDriver , private val coroutineScope : CoroutineScope ) {
254
+ class SqlDelightDataSource (private val database : Database ) {
190
255
191
- private var database= Database (sqlDriver)
192
- private var quizQueries= database.quizDatabaseQueries
193
256
257
+ private var quizQueries = database.quizQuestionQueries
194
258
195
- suspend fun getUpdateTimeStamp ():Long = quizQueries.selectUpdateTimestamp().awaitAsOneOrNull()?.timestamprequest ? : 0L
259
+ suspend fun getAllQuestions (): List <Question > {
260
+ return quizQueries.selectAllQuestionsWithAnswers().awaitAsList()
196
261
262
+ .groupBy { it.question_id }
263
+ .map { (questionId, rowList) ->
197
264
198
- suspend fun setUpdateTimeStamp (timeStamp : Long ) {
199
- quizQueries.deleteTimeStamp()
200
- quizQueries.insertTimeStamp(timeStamp)
265
+ Question (
266
+ id = questionId,
267
+ label = rowList.first().label,
268
+ correctAnswerId = rowList.first().correctAnswerId,
269
+ answers = rowList.map { answer ->
270
+ Answer (
271
+ id = answer.id_,
272
+ label = answer.label_
273
+ )
274
+ }
275
+ )
276
+ }
201
277
}
202
278
203
- suspend fun getAllQuestions (): List <Question > {
204
- return quizQueries.selectAllQuestionsWithAnswers().awaitAsList()
205
-
206
- .groupBy {it.question_id }
207
- .map { (questionId, rowList) ->
208
279
209
- Question (
210
- id = questionId,
211
- label = rowList.first().label,
212
- correctAnswerId = rowList.first().correctAnswerId,
213
- answers = rowList.map { answer ->
214
- Answer (
215
- id = answer.id_,
216
- label = answer.label_
217
- )
218
- }
219
- )
220
- }
221
- }
222
-
223
-
224
-
225
- suspend fun insertQuestions (questions : List <Question >) {
280
+ suspend fun insertQuestions (questions : List <Question >) {
226
281
quizQueries.deleteQuestions();
227
282
quizQueries.deleteAnswers()
228
- questions.forEach {question ->
283
+ questions.forEach { question ->
229
284
quizQueries.insertQuestion(question.id, question.label, question.correctAnswerId)
230
- question.answers.forEach {answer ->
231
- quizQueries.insertAnswer(answer.id,answer.label,question.id)
285
+ question.answers.forEach { answer ->
286
+ quizQueries.insertAnswer(answer.id, answer.label, question.id)
232
287
}
233
288
}
234
289
}
290
+
291
+ suspend fun resetQuestions () {
292
+ quizQueries.deleteQuestions()
293
+ quizQueries.deleteAnswers()
294
+ }
235
295
}
236
296
```
237
297
:::
238
298
239
299
::: details QuizRepository.kt
240
300
``` kotlin
241
- package network
242
-
243
- import app.cash.sqldelight.db.SqlDriver
244
- import kotlinx.coroutines.CoroutineScope
245
- import kotlinx.coroutines.Dispatchers
246
- import kotlinx.coroutines.flow.MutableStateFlow
247
- import kotlinx.coroutines.flow.update
248
- import kotlinx.coroutines.launch
249
- import kotlinx.datetime.Clock
250
- import network.data.Question
251
-
252
-
253
- class QuizRepository (sqlDriver : SqlDriver ) {
254
-
301
+ class QuizRepository {
255
302
private val mockDataSource = MockDataSource ()
256
- private val quizAPI = QuizApiDatasource ()
257
- private val coroutineScope = CoroutineScope ( Dispatchers . Main )
258
- private var quizDB = QuizDbDataSource (sqlDriver,coroutineScope )
303
+ private val quizApiDatasource = QuizApiDatasource ()
304
+ private var quizKStoreDataSource = KStoreDataSource ( AppInitializer .getKStoreInstance() )
305
+ private var sqlDelightDataSource = SqlDelightDataSource ( AppInitializer .getDatabase() !! )
259
306
260
- private var _questionState = MutableStateFlow (listOf<Question >())
261
- val questionState get() = _questionState
307
+ private suspend fun fetchQuiz (): List <Question > = quizApiDatasource.getAllQuestions().questions
262
308
263
- init {
264
- updateQuiz()
265
- }
266
-
267
- private suspend fun fetchQuiz (): List <Question > = quizAPI.getAllQuestions().questions
309
+ @OptIn(ExperimentalTime ::class )
310
+ private suspend fun fetchAndStoreQuiz (): List <Question > {
311
+ sqlDelightDataSource.resetQuestions()
268
312
269
- private suspend fun fetchAndStoreQuiz (): List <Question >{
270
- val questions = fetchQuiz()
271
- quizDB.insertQuestions(questions)
272
- quizDB.setUpdateTimeStamp(Clock .System .now().epochSeconds)
313
+ val questions = fetchQuiz()
314
+ sqlDelightDataSource.insertQuestions(questions)
315
+ quizKStoreDataSource.setUpdateTimeStamp(Clock .System .now().epochSeconds)
273
316
return questions
274
317
}
275
- private fun updateQuiz (){
276
-
277
-
278
- coroutineScope.launch {
279
- _questionState .update {
280
- try {
281
- val lastRequest = quizDB.getUpdateTimeStamp()
282
- if (lastRequest == 0L || lastRequest - Clock .System .now().epochSeconds > 300000 ){
283
- fetchAndStoreQuiz()
284
- }else {
285
- quizDB.getAllQuestions()
286
- }
287
- } catch (e: NullPointerException ) {
288
- fetchAndStoreQuiz()
289
- } catch (e: Exception ) {
290
- e.printStackTrace()
291
- mockDataSource.generateDummyQuestionsList()
292
- }
293
318
319
+ @OptIn(ExperimentalTime ::class )
320
+ suspend fun updateQuiz (): List <Question > {
321
+ try {
322
+ val lastRequest = quizKStoreDataSource.getUpdateTimeStamp()
323
+ return if (lastRequest == 0L || lastRequest - Clock .System .now().epochSeconds > 300000 ) {
324
+ fetchAndStoreQuiz()
325
+ } else {
326
+ // quizKStoreDataSource.getAllQuestions()
327
+ sqlDelightDataSource.getAllQuestions()
294
328
}
329
+ } catch (e: NullPointerException ) {
330
+ return fetchAndStoreQuiz()
331
+ } catch (e: Exception ) {
332
+ e.printStackTrace()
333
+ return mockDataSource.generateDummyQuestionsList()
295
334
}
296
335
}
336
+
297
337
}
298
338
```
299
339
:::
300
340
301
-
302
-
303
341
::: tip More databases options
304
342
For not using SQLight ORM, you can use [ ` Realm kotlin ` ] ( https://github.com/realm/realm-kotlin ) or [ KStore] ( https://github.com/xxfast/KStore )
305
343
:::
0 commit comments