Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the orderNumber field for sorting #222

Merged
merged 2 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
661 changes: 661 additions & 0 deletions app/schemas/ro.code4.monitorizarevot.data.AppDatabase/4.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,51 @@ class MigrationTest {
}
}

@Test
fun migrate3To4() {
check3To4MigrationFor(ContentValues().apply {
put("uniqueId", "unique_section")
put("formId", 100)
}, "section", 5, "SELECT * FROM section")
check3To4MigrationFor(ContentValues().apply {
put("id", 100)
put("text", "question_text")
put("code", "question_code")
put("questionType", 0)
put("sectionId", "section_id")
put("hasNotes", false)
}, "question", 7, "SELECT * FROM question")
check3To4MigrationFor(ContentValues().apply {
put("idOption", 100)
put("text", "answer_text")
put("isFreeText", false)
put("questionId", 0)
}, "answer", 5, "SELECT * FROM answer")
}

private fun check3To4MigrationFor(
testValues: ContentValues,
tableName: String,
nrOfColumns: Int,
query: String
) {
helper.createDatabase(TEST_DB, 3).use {
val rowId = it.insert(tableName, SQLiteDatabase.CONFLICT_FAIL, testValues)
assertTrue(rowId > 0)
}
val db = helper.runMigrationsAndValidate(TEST_DB, 4, true, Migrations.MIGRATION_3_4)
val sectionsCursor = db.query(query)
assertNotNull(sectionsCursor)
sectionsCursor.use {
// we have a single row, previously inserted
assertEquals(1, it.count)
assertTrue(it.moveToFirst())
assertEquals(nrOfColumns, it.columnCount)
// check for the new column "orderNumber" and that it has the default value of 0
assertEquals(0, it.getInt(it.getColumnIndex("orderNumber")))
}
}

@Test
@Throws(IOException::class)
fun migrateAll() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ro.code4.monitorizarevot.data.model.answers.SelectedAnswer

@Database(
entities = [County::class, PollingStation::class, FormDetails::class, Section::class, Question::class, Answer::class, AnsweredQuestion::class, SelectedAnswer::class, Note::class],
version = 3
version = 4
)
@TypeConverters(DateConverter::class)
abstract class AppDatabase : RoomDatabase() {
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/java/ro/code4/monitorizarevot/data/Migrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,16 @@ object Migrations {
}
}

val ALL: Array<Migration> = arrayOf(MIGRATION_1_2, MIGRATION_2_3)
/**
* This migration changes the database to add the new orderNumber for sections, questions and answers.
*/
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE section ADD COLUMN `orderNumber` INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE question ADD COLUMN `orderNumber` INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE answer ADD COLUMN `orderNumber` INTEGER NOT NULL DEFAULT 0")
}
}

val ALL: Array<Migration> = arrayOf(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.room.*
import androidx.room.OnConflictStrategy.REPLACE
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Observable
import ro.code4.monitorizarevot.data.model.Answer
import ro.code4.monitorizarevot.data.model.FormDetails
import ro.code4.monitorizarevot.data.model.Question
Expand All @@ -14,7 +15,7 @@ import ro.code4.monitorizarevot.data.model.answers.SelectedAnswer
import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO
import ro.code4.monitorizarevot.data.pojo.FormWithSections
import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions
import java.util.*


@Dao
interface FormsDao {
Expand Down Expand Up @@ -64,21 +65,21 @@ interface FormsDao {
fun getAnswersFor(
countyCode: String,
pollingStationNumber: Int
): LiveData<List<AnsweredQuestionPOJO>>
): Observable<List<AnsweredQuestionPOJO>>


@Query("SELECT * FROM form_details ORDER BY `order`")
fun getFormsWithSections(): LiveData<List<FormWithSections>>
fun getFormsWithSections(): Observable<List<FormWithSections>>

@Query("SELECT * FROM section where formId=:formId")
fun getSectionsWithQuestions(formId: Int): LiveData<List<SectionWithQuestions>>
@Query("SELECT * FROM section where formId=:formId ORDER BY orderNumber")
fun getSectionsWithQuestions(formId: Int): Observable<List<SectionWithQuestions>>

@Query("SELECT * FROM answered_question WHERE countyCode=:countyCode AND pollingStationNumber=:pollingStationNumber AND formId=:formId")
fun getAnswersForForm(
countyCode: String?,
pollingStationNumber: Int,
formId: Int
): LiveData<List<AnsweredQuestionPOJO>>
): Observable<List<AnsweredQuestionPOJO>>

@Query("SELECT * FROM answered_question WHERE countyCode=:countyCode AND pollingStationNumber=:pollingStationNumber AND formId=:formId AND synced=:synced")
fun getNotSyncedQuestionsForForm(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Answer {
@Ignore
var value: String? = null

@Expose
var orderNumber = 0

override fun equals(other: Any?): Boolean =
other is Answer && idOption == other.idOption && text == other.text &&
isFreeText == other.isFreeText && questionId == other.questionId &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class Question {

var hasNotes = false

@Expose
var orderNumber = 0

override fun equals(other: Any?): Boolean =
other is Question && id == other.id && text == other.text && code == other.code &&
questionType == other.questionType &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Section {

var formId: Int = -1

@Expose
var orderNumber = 0

override fun equals(other: Any?): Boolean {
if (other !is Section) {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ class Repository : KoinComponent {
observableApi.onErrorReturnItem(emptyList()),
BiFunction<List<County>, List<County>, List<County>> { dbCounties, apiCounties ->
val areAllApiCountiesInDb = apiCounties.all(dbCounties::contains)
apiCounties.forEach { it.name = it.name.toLowerCase(Locale.getDefault()).capitalize() }
apiCounties.forEach {
it.name = it.name.toLowerCase(Locale.getDefault()).capitalize()
}
return@BiFunction when {
apiCounties.isNotEmpty() && !areAllApiCountiesInDb -> {
db.countyDao().deleteAll()
Expand Down Expand Up @@ -113,27 +115,23 @@ class Repository : KoinComponent {
fun getAnswers(
countyCode: String,
pollingStationNumber: Int
): LiveData<List<AnsweredQuestionPOJO>> =
): Observable<List<AnsweredQuestionPOJO>> =
db.formDetailsDao().getAnswersFor(countyCode, pollingStationNumber)

fun getFormsWithQuestions(): LiveData<List<FormWithSections>> =
fun getFormsWithQuestions(): Observable<List<FormWithSections>> =
db.formDetailsDao().getFormsWithSections()

fun getSectionsWithQuestions(formId: Int): LiveData<List<SectionWithQuestions>> =
fun getSectionsWithQuestions(formId: Int): Observable<List<SectionWithQuestions>> =
db.formDetailsDao().getSectionsWithQuestions(formId)

fun getForms(): Observable<Unit> {

val observableDb = db.formDetailsDao().getAllForms().toObservable()

val observableDb = db.formDetailsDao().getFormsWithSections()
val observableApi = apiInterface.getForms()

return Observable.zip(
observableDb.onErrorReturn { null },
observableApi.onErrorReturn { null },
BiFunction<List<FormDetails>?, VersionResponse?, Unit> { dbFormDetails, response ->
BiFunction<List<FormWithSections>?, VersionResponse?, Unit> { dbFormDetails, response ->
processFormDetailsData(dbFormDetails, response)

})
}

Expand Down Expand Up @@ -164,7 +162,7 @@ class Repository : KoinComponent {
}

private fun processFormDetailsData(
dbFormDetails: List<FormDetails>?,
dbFormDetails: List<FormWithSections>?,
response: VersionResponse?
) {
if (response == null) {
Expand All @@ -176,17 +174,17 @@ class Repository : KoinComponent {
return
}
if (apiFormDetails.size < dbFormDetails.size) {
dbFormDetails.minus(apiFormDetails).also { diff ->
dbFormDetails.map { it.form }.minus(apiFormDetails).also { diff ->
if (diff.isNotEmpty()) {
deleteFormDetails(*diff.map { it }.toTypedArray())
}
}
}
apiFormDetails.forEach { apiForm ->
val dbForm = dbFormDetails.find { it.id == apiForm.id }
if (dbForm != null && (apiForm.formVersion != dbForm.formVersion ||
apiForm.order != dbForm.order)) {
deleteFormDetails(dbForm)
val dbForm = dbFormDetails.find { it.form.id == apiForm.id }
if (dbForm != null && (apiForm.formVersion != dbForm.form.formVersion ||
apiForm.order != dbForm.form.order)) {
deleteFormDetails(dbForm.form)
saveFormDetails(apiForm)
}
if (dbForm == null) {
Expand Down Expand Up @@ -219,7 +217,7 @@ class Repository : KoinComponent {
countyCode: String?,
pollingStationNumber: Int,
formId: Int
): LiveData<List<AnsweredQuestionPOJO>> {
): Observable<List<AnsweredQuestionPOJO>> {
return db.formDetailsDao().getAnswersForForm(countyCode, pollingStationNumber, formId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import ro.code4.monitorizarevot.adapters.helper.AddNoteListItem
import ro.code4.monitorizarevot.adapters.helper.FormListItem
Expand All @@ -18,7 +20,6 @@ import ro.code4.monitorizarevot.data.pojo.FormWithSections
import ro.code4.monitorizarevot.data.pojo.PollingStationInfo
import ro.code4.monitorizarevot.helper.Constants.REMOTE_CONFIG_FILTER_DIASPORA_FORMS
import ro.code4.monitorizarevot.helper.completedPollingStationConfig
import ro.code4.monitorizarevot.helper.zipLiveData
import ro.code4.monitorizarevot.ui.base.BaseFormViewModel

class FormsViewModel : BaseFormViewModel() {
Expand Down Expand Up @@ -55,12 +56,15 @@ class FormsViewModel : BaseFormViewModel() {
update()
}

zipLiveData(
disposables.add(Observable.combineLatest(
repository.getAnswers(countyCode, pollingStationNumber),
repository.getFormsWithQuestions()
).observeForever {
repository.getFormsWithQuestions(),
BiFunction<List<AnsweredQuestionPOJO>, List<FormWithSections>, Pair<List<AnsweredQuestionPOJO>, List<FormWithSections>>> {
t1, t2 -> Pair(t1, t2)
}
).subscribe {
processList(it.first, it.second)
}
})
}

fun forms(): LiveData<ArrayList<ListItem>> = formsLiveData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,48 @@ package ro.code4.monitorizarevot.ui.forms.questions

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import ro.code4.monitorizarevot.adapters.helper.ListItem
import ro.code4.monitorizarevot.data.model.FormDetails
import ro.code4.monitorizarevot.data.pojo.AnsweredQuestionPOJO
import ro.code4.monitorizarevot.data.pojo.SectionWithQuestions
import ro.code4.monitorizarevot.helper.zipLiveData
import ro.code4.monitorizarevot.ui.base.BaseFormViewModel

abstract class BaseQuestionViewModel : BaseFormViewModel() {

val questionsLiveData = MutableLiveData<ArrayList<ListItem>>()

var selectedFormId: Int = -1

fun questions(): LiveData<ArrayList<ListItem>> = questionsLiveData

private fun getQuestions(formId: Int) {

selectedFormId = formId
zipLiveData(
disposables.add(Observable.combineLatest(
repository.getSectionsWithQuestions(formId),
repository.getAnswersForForm(countyCode, pollingStationNumber, formId)
).observeForever {
processList(it.first, it.second)
}

repository.getAnswersForForm(countyCode, pollingStationNumber, formId),
BiFunction<List<SectionWithQuestions>, List<AnsweredQuestionPOJO>, Pair<List<SectionWithQuestions>, List<AnsweredQuestionPOJO>>> { t1, t2 ->
Pair(t1, t2)
}
).subscribeOn(Schedulers.computation())
.map { dataPair ->
// sort on orderNumber the sections along with their questions and answers
val sortedSections = dataPair.first.sortedBy { it.section.orderNumber }
for (sortedSection in sortedSections) {
val sortedQuestions =
sortedSection.questions.sortedBy { it.question.orderNumber }
for (sortedQuestion in sortedQuestions) {
sortedQuestion.answers = sortedQuestion.answers?.sortedBy { it.orderNumber }
}
sortedSection.questions = sortedQuestions
}
Pair(sortedSections, dataPair.second)
}.observeOn(AndroidSchedulers.mainThread())
.subscribe { result -> processList(result.first, result.second) })
}

// TODO this method should also run on a background thread to avoid doing lengthy
// operations(like sorting or iterating over large collections) on the main thread
abstract fun processList(
sections: List<SectionWithQuestions>,
answersForForm: List<AnsweredQuestionPOJO>
Expand Down