From 23361ccddcf7a774de54afce05932aaa5fffdfb1 Mon Sep 17 00:00:00 2001 From: Amr Hossam Date: Mon, 19 Aug 2024 22:21:04 +0300 Subject: [PATCH] Implement saving of completed surveys to RoomD Signed-off-by: Amr Hossam --- onebusaway-android/build.gradle | 5 ++ .../android/database/AppDatabase.kt | 14 +++++ .../android/ui/survey/SurveyLocalData.java | 42 --------------- .../android/ui/survey/SurveyManager.java | 4 +- .../android/ui/survey/dao/StudiesDao.kt | 22 ++++++++ .../android/ui/survey/dao/SurveysDao.kt | 29 +++++++++++ .../android/ui/survey/entity/Study.kt | 12 +++++ .../android/ui/survey/entity/Survey.kt | 17 +++++++ .../ui/survey/repository/SurveyRepository.kt | 46 +++++++++++++++++ .../android/ui/survey/utils/SurveyDbHelper.kt | 51 +++++++++++++++++++ .../android/ui/survey/utils/SurveyUtils.java | 23 +++------ 11 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/database/AppDatabase.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/StudiesDao.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/SurveysDao.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Study.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/repository/SurveyRepository.kt create mode 100644 onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt diff --git a/onebusaway-android/build.gradle b/onebusaway-android/build.gradle index 1c06c7297..f5e5bb618 100644 --- a/onebusaway-android/build.gradle +++ b/onebusaway-android/build.gradle @@ -20,6 +20,7 @@ import org.apache.tools.ant.filters.ConcatFilter apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' repositories { maven { @@ -368,6 +369,10 @@ dependencies { implementation "androidx.concurrent:concurrent-listenablefuture-callback:1.0.0-beta01" implementation "androidx.core:core-ktx:1.7.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + // RoomDB + implementation "androidx.room:room-runtime:2.5.0" + kapt "androidx.room:room-compiler:2.5.0" + implementation "androidx.room:room-ktx:2.5.0" } diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/database/AppDatabase.kt b/onebusaway-android/src/main/java/org/onebusaway/android/database/AppDatabase.kt new file mode 100644 index 000000000..89991055d --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/database/AppDatabase.kt @@ -0,0 +1,14 @@ +package org.onebusaway.android.database + +import androidx.room.Database +import androidx.room.RoomDatabase +import org.onebusaway.android.ui.survey.dao.StudiesDao +import org.onebusaway.android.ui.survey.dao.SurveysDao +import org.onebusaway.android.ui.survey.entity.Study +import org.onebusaway.android.ui.survey.entity.Survey + +@Database(entities = [Study::class, Survey::class], version = 1) +abstract class AppDatabase : RoomDatabase() { + abstract fun studiesDao(): StudiesDao + abstract fun surveysDao(): SurveysDao +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java index 29f5b5be5..032e06215 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java @@ -11,50 +11,8 @@ public class SurveyLocalData { private static final String PREFS_NAME = "survey_pref"; - private static final String HASH_MAP_KEY = "survey_hash_map"; private static final String UUID_KEY = "my_uuid"; - /** - * Saves the answered surveys to SharedPreferences. - * The HashMap contains survey IDs as keys and their completion status as boolean values. - * - * @param context The context used to access SharedPreferences. - * @param hashMap A HashMap where the keys are survey IDs and the values are booleans indicating completion status. - */ - public static void setCompletedSurveys(Context context, HashMap hashMap) { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - JSONObject jsonObject = new JSONObject(hashMap); - String jsonString = jsonObject.toString(); - editor.putString(HASH_MAP_KEY, jsonString); - editor.apply(); - } - - /** - * Retrieves the answered surveys from SharedPreferences as a HashMap. - * The map contains survey IDs as keys and their completion status as boolean values. - * - * @param context The context used to access SharedPreferences. - * @return A HashMap where the keys are survey IDs and the values are booleans indicating completion status. - */ - public static HashMap getCompletedSurveys(Context context) { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String jsonString = prefs.getString(HASH_MAP_KEY, "{}"); - HashMap hashMap = new HashMap<>(); - try { - JSONObject jsonObject = new JSONObject(jsonString); - Iterator keys = jsonObject.keys(); - while (keys.hasNext()) { - String key = keys.next(); - boolean value = jsonObject.getBoolean(key); - hashMap.put(key, value); - } - } catch (JSONException e) { - e.printStackTrace(); - } - return hashMap; - } - public static void saveUserUUID(Context context, UUID uuid) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java index 89a63a43a..dd1404220 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java @@ -27,6 +27,7 @@ import org.onebusaway.android.io.request.survey.model.SubmitSurveyResponse; import org.onebusaway.android.io.request.survey.submit.ObaSubmitSurveyRequest; import org.onebusaway.android.io.request.survey.submit.SubmitSurveyRequestListener; +import org.onebusaway.android.ui.survey.utils.SurveyDbHelper; import org.onebusaway.android.ui.survey.utils.SurveyUtils; import org.onebusaway.android.ui.survey.utils.SurveyViewUtils; import org.onebusaway.android.ui.survey.activities.SurveyWebViewActivity; @@ -292,7 +293,8 @@ private void handleExternalSurvey() { * Mark current survey as done */ public void handleCompleteSurvey() { - SurveyUtils.markSurveyAsCompleted(context, String.valueOf(curSurveyID)); + StudyResponse.Surveys currentSurvey = mStudyResponse.getSurveys().get(curSurveyIndex); + SurveyDbHelper.markSurveyAsCompleted(context, currentSurvey); // Remove the hero question view if (isVisibleOnStops) arrivalsList.removeHeaderView(surveyView); } diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/StudiesDao.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/StudiesDao.kt new file mode 100644 index 000000000..6796c746a --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/StudiesDao.kt @@ -0,0 +1,22 @@ +package org.onebusaway.android.ui.survey.dao + +import androidx.room.* +import org.onebusaway.android.ui.survey.entity.Study + +@Dao +interface StudiesDao { + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertStudy(study: Study): Long + + @Update + suspend fun updateStudy(study: Study) + + @Delete + suspend fun deleteStudy(study: Study) + + @Query("SELECT * FROM studies WHERE study_id = :studyId") + suspend fun getStudyById(studyId: Int): Study? + + @Query("SELECT * FROM studies") + suspend fun getAllStudies(): List +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/SurveysDao.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/SurveysDao.kt new file mode 100644 index 000000000..62afb8c88 --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/dao/SurveysDao.kt @@ -0,0 +1,29 @@ +package org.onebusaway.android.ui.survey.dao + +import androidx.room.* +import org.onebusaway.android.ui.survey.entity.Survey + +@Dao +interface SurveysDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertSurvey(survey: Survey): Long + + @Update + suspend fun updateSurvey(survey: Survey) + + @Delete + suspend fun deleteSurvey(survey: Survey) + + @Query("SELECT *FROM surveys") + suspend fun getAllSurveys():List + + @Query("SELECT * FROM surveys WHERE survey_id = :surveyId") + suspend fun getSurveyById(surveyId: Int): Survey? + + @Query("SELECT * FROM surveys WHERE study_id = :studyId") + suspend fun getSurveysByStudyId(studyId: Int): List + + @Query("SELECT COUNT(*) > 0 FROM surveys WHERE survey_id = :surveyId") + suspend fun isSurveyIdExists(surveyId: Int): Boolean + +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Study.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Study.kt new file mode 100644 index 000000000..2883a9601 --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Study.kt @@ -0,0 +1,12 @@ +package org.onebusaway.android.ui.survey.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "studies") +data class Study( + @PrimaryKey val study_id: Int, + val name: String, + val description: String, + val is_subscribed: Boolean +) diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt new file mode 100644 index 000000000..c4e79294b --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt @@ -0,0 +1,17 @@ +package org.onebusaway.android.ui.survey.entity + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey + +@Entity( + tableName = "surveys", foreignKeys = [ForeignKey( + entity = Study::class, + parentColumns = ["study_id"], + childColumns = ["study_id"], + onDelete = ForeignKey.CASCADE + )] +) +data class Survey( + @PrimaryKey val survey_id: Int, val study_id: Int, val name: String +) diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/repository/SurveyRepository.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/repository/SurveyRepository.kt new file mode 100644 index 000000000..9e049c844 --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/repository/SurveyRepository.kt @@ -0,0 +1,46 @@ +package org.onebusaway.android.ui.survey.repository + +import android.content.Context +import androidx.room.Room +import org.onebusaway.android.database.AppDatabase +import org.onebusaway.android.ui.survey.entity.Study +import org.onebusaway.android.ui.survey.entity.Survey + + +class SurveyRepository(context: Context) { + private val db: AppDatabase = Room.databaseBuilder( + context.applicationContext, AppDatabase::class.java, "study-survey-db" + ).build() + + private val studiesDao = db.studiesDao() + private val surveysDao = db.surveysDao() + + suspend fun addOrUpdateStudy(study: Study) { + val existingStudy = studiesDao.getStudyById(study.study_id) + if (existingStudy == null) { + studiesDao.insertStudy(study) + } else { + studiesDao.updateStudy(study) + } + } + + suspend fun getAllStudies(): List { + return studiesDao.getAllStudies() + } + + suspend fun addSurvey(survey: Survey) { + surveysDao.insertSurvey(survey) + } + + suspend fun getSurveysForStudy(studyId: Int): List { + return surveysDao.getSurveysByStudyId(studyId) + } + + suspend fun checkSurveyCompleted(surveyId: Int): Boolean { + return surveysDao.isSurveyIdExists(surveyId) + } + + suspend fun getAllSurveys(): List { + return surveysDao.getAllSurveys(); + } +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt new file mode 100644 index 000000000..5e9f5a87e --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt @@ -0,0 +1,51 @@ +package org.onebusaway.android.ui.survey.utils + +import android.content.Context +import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.onebusaway.android.io.request.survey.model.StudyResponse +import org.onebusaway.android.ui.survey.entity.Study +import org.onebusaway.android.ui.survey.entity.Survey +import org.onebusaway.android.ui.survey.repository.SurveyRepository + +class SurveyDbHelper { + companion object { + private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + @JvmStatic + fun markSurveyAsCompleted(context: Context, survey: StudyResponse.Surveys) { + val surveyRepo = SurveyRepository(context) + + val newStudy = Study( + survey.study.id, survey.study.name, survey.study.description, true + ) + val newSurvey = Survey(survey.id, survey.study.id, survey.name) + coroutineScope.launch { + try { + surveyRepo.addOrUpdateStudy(newStudy) + surveyRepo.addSurvey(newSurvey) + Log.d("All Saved Surveys", surveyRepo.getAllSurveys().toString()); + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + @JvmStatic + fun isSurveyCompleted(context: Context, surveyId: Int): Boolean { + val surveyRepo = SurveyRepository(context) + return runBlocking { + try { + surveyRepo.checkSurveyCompleted(surveyId) + } catch (e: Exception) { + e.printStackTrace() + false + } + } + } + } +} \ No newline at end of file diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java index 9c136df30..3e68be26a 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java @@ -18,7 +18,6 @@ import org.onebusaway.android.ui.survey.SurveyLocalData; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -83,9 +82,6 @@ public static String getTextInputAnswer(View surveyView) { * @return The zero-based index of the current survey, or -1 if all surveys are completed or filtered out. */ public static Integer getCurrentSurveyIndex(StudyResponse studyResponse, Context context, Boolean isVisibleOnStop, ObaStop currentStop) { - // Map of completed surveys with survey IDs as keys and completion status as values - HashMap completedSurveysMap = SurveyLocalData.getCompletedSurveys(context); - List surveys = studyResponse.getSurveys(); // Iterate through the surveys to find the first uncompleted one @@ -97,7 +93,7 @@ public static Integer getCurrentSurveyIndex(StudyResponse studyResponse, Context List visibleRouteList = surveys.get(index).getVisible_route_list(); // Skip if there is not questions - if(surveys.get(index).getQuestions().isEmpty()) continue; + if (surveys.get(index).getQuestions().isEmpty()) continue; // Skip this survey if it shouldn't be shown on either map or stops if (!showQuestionOnStops && !showQuestionOnMaps) { @@ -116,8 +112,12 @@ public static Integer getCurrentSurveyIndex(StudyResponse studyResponse, Context if (!showQuestionOnMaps) continue; } + boolean isSurveyCompleted = SurveyDbHelper.isSurveyCompleted(context,surveys.get(index).getId()); + + Log.d("isSurveyCompleted",isSurveyCompleted + " "); + // Return the index if the survey is uncompleted - if (completedSurveysMap.get(surveys.get(index).getId().toString()) == null) { + if (!isSurveyCompleted) { return index; } } @@ -128,13 +128,13 @@ public static Integer getCurrentSurveyIndex(StudyResponse studyResponse, Context /** * Determines whether to show a survey for the given stop based on the provided visible stops and routes lists. * - * @param currentStop The current stop for which the survey visibility is being checked. + * @param currentStop The current stop for which the survey visibility is being checked. * @param visibleStopsList A list of stop IDs where the survey should be shown. Can be null. * @param visibleRouteList A list of route IDs where the survey should be shown. Can be null. * @return true if the survey should be shown for the current stop, otherwise false. */ private static boolean showSurvey(ObaStop currentStop, List visibleStopsList, List visibleRouteList) { - if(currentStop == null || currentStop.getId() == null) return false; + if (currentStop == null || currentStop.getId() == null) return false; // If both visibleStopsList and visibleRouteList are null, show the survey by default. if (visibleRouteList == null && visibleStopsList == null) { return true; @@ -158,13 +158,6 @@ private static boolean showSurvey(ObaStop currentStop, List visibleStops return false; } - public static void markSurveyAsCompleted(Context context, String surveyID) { - HashMap completedSurveys = SurveyLocalData.getCompletedSurveys(context); - completedSurveys.put(surveyID, true); - // Save to the local storage - SurveyLocalData.setCompletedSurveys(context, completedSurveys); - } - /** * Extracts answers from a question view and constructs a JSON array as a request body. *