From 509a0434c54453067c029977eb31b4f9a504e969 Mon Sep 17 00:00:00 2001 From: denizk0461 Date: Fri, 28 Apr 2023 15:14:50 +0200 Subject: [PATCH] Possibly fixed-for-good a bug that caused events to be fetched but not stored properly in the database; I believe this was caused by FetcherActivity.kt sequentially executing nukeEvents() and insertEvents(), but doing both asynchronously, and individually, which could mean that insertEvents() finishes before nukeEvents() and therefore nukeEvents() would delete the old events as well as the new ones. This doesn't seem entirely logical to me, as no ConflictStrategy has been configured, and inserting new events before the old ones have been cleared (since they both start their primary keys at 0) would have crashed the app. --- .../studip/activity/FetcherActivity.kt | 36 ++++++++++--------- .../studip/activity/MainActivity.kt | 10 +++--- .../studip/adapter/StudIPEventItemAdapter.kt | 2 +- .../denizk0461/studip/data/Dependencies.kt | 15 -------- .../denizk0461/studip/data/StudIPParser.kt | 13 +++++-- .../com/denizk0461/studip/data/StwParser.kt | 13 +++++-- .../java/com/denizk0461/studip/db/AppDAO.kt | 6 ++-- .../com/denizk0461/studip/db/AppDatabase.kt | 2 +- .../com/denizk0461/studip/db/AppRepository.kt | 21 +++++++++++ .../studip/fragment/EventPageFragment.kt | 2 +- .../denizk0461/studip/model/StudIPEvent.kt | 10 +++--- .../studip/sheet/ScheduleUpdateSheet.kt | 2 +- .../studip/viewmodel/AppViewModel.kt | 3 +- .../studip/viewmodel/CanteenViewModel.kt | 2 +- .../studip/viewmodel/FetcherViewModel.kt | 14 ++------ 15 files changed, 82 insertions(+), 69 deletions(-) delete mode 100644 app/src/main/java/com/denizk0461/studip/data/Dependencies.kt diff --git a/app/src/main/java/com/denizk0461/studip/activity/FetcherActivity.kt b/app/src/main/java/com/denizk0461/studip/activity/FetcherActivity.kt index 84ae114..b48c279 100644 --- a/app/src/main/java/com/denizk0461/studip/activity/FetcherActivity.kt +++ b/app/src/main/java/com/denizk0461/studip/activity/FetcherActivity.kt @@ -6,10 +6,8 @@ import android.os.Bundle import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import android.widget.Toast import androidx.lifecycle.ViewModelProvider import com.denizk0461.studip.R -import com.denizk0461.studip.data.StudIPParser import com.denizk0461.studip.data.getThemedColor import com.denizk0461.studip.databinding.ActivityFetcherBinding import com.denizk0461.studip.viewmodel.FetcherViewModel @@ -72,21 +70,25 @@ class FetcherActivity : Activity() { ) { p0 -> try { // Decode HTML and parse it into a list of StudIPEvent.kt - StudIPParser().parse(URLDecoder.decode(p0, "UTF-8")) { events -> - // Delete all previously fetched elements - viewModel.nukeEvents() - - // Insert the list into the database - viewModel.insertEvents(events) - - // Notify the user that the fetch was successful - Toast.makeText( - this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT - ).show() - - // Close the activity - finish() - } + // TODO this exception handling and finish() is broken + viewModel.parse(URLDecoder.decode(p0, "UTF-8")) +// StudIPParser(application).parse(URLDecoder.decode(p0, "UTF-8")) { events -> +// +// // Delete all previously fetched elements +//// viewModel.nukeEvents() +//// viewModel.replaceEvents(events) +// +// // Insert the list into the database +//// viewModel.insertEvents(events) +// +// // Notify the user that the fetch was successful +// Toast.makeText( +// this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT +// ).show() +// +// // Close the activity +// finish() +// } } catch (e: IOException) { // Let the user know that an error occurred diff --git a/app/src/main/java/com/denizk0461/studip/activity/MainActivity.kt b/app/src/main/java/com/denizk0461/studip/activity/MainActivity.kt index c9dabbc..05ac569 100644 --- a/app/src/main/java/com/denizk0461/studip/activity/MainActivity.kt +++ b/app/src/main/java/com/denizk0461/studip/activity/MainActivity.kt @@ -1,12 +1,11 @@ package com.denizk0461.studip.activity import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat +import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.Lifecycle import com.denizk0461.studip.R -import com.denizk0461.studip.data.Dependencies import com.denizk0461.studip.databinding.ActivityMainBinding import com.denizk0461.studip.db.AppRepository import com.denizk0461.studip.fragment.EventFragment @@ -17,7 +16,7 @@ import com.denizk0461.studip.model.SettingsPreferences /** * Main activity that handles all common fragments. This is opened on app launch. */ -class MainActivity : AppCompatActivity() { +class MainActivity : FragmentActivity() { // View binding private lateinit var binding: ActivityMainBinding @@ -36,7 +35,7 @@ class MainActivity : AppCompatActivity() { * Instantiate repository object that is accessed by the fragments' view models to retrieve * data. */ - Dependencies.repo = AppRepository(application) +// Dependencies.repo = AppRepository(application) // Inflate view binding and bind to this activity binding = ActivityMainBinding.inflate(layoutInflater) @@ -44,7 +43,8 @@ class MainActivity : AppCompatActivity() { // Open the fragment that the user specified to open on app start if ( - Dependencies.repo.getBooleanPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START) + AppRepository.getRepositoryInstance(application) + .getBooleanPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START) ) { // Open canteen fragment binding.contentMain.navView.selectedItemId = R.id.food diff --git a/app/src/main/java/com/denizk0461/studip/adapter/StudIPEventItemAdapter.kt b/app/src/main/java/com/denizk0461/studip/adapter/StudIPEventItemAdapter.kt index d54122f..02c3cdd 100644 --- a/app/src/main/java/com/denizk0461/studip/adapter/StudIPEventItemAdapter.kt +++ b/app/src/main/java/com/denizk0461/studip/adapter/StudIPEventItemAdapter.kt @@ -27,7 +27,7 @@ class StudIPEventItemAdapter( ) : RecyclerView.Adapter() { /** - * List of all events (still not filtered by day at this point) + * List of all events. */ private var events: MutableList = mutableListOf() diff --git a/app/src/main/java/com/denizk0461/studip/data/Dependencies.kt b/app/src/main/java/com/denizk0461/studip/data/Dependencies.kt deleted file mode 100644 index 0c38b68..0000000 --- a/app/src/main/java/com/denizk0461/studip/data/Dependencies.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.denizk0461.studip.data - -import com.denizk0461.studip.db.AppRepository - -/** - * Static object that holds a reference to the app's repository to provide abstracted access to the - * app database. - */ -object Dependencies { - /** - * Reference to the app's repository. Since it instantiated once by the main activity, it can be - * accessed statically by all other classes. Used for database transactions. - */ - lateinit var repo: AppRepository -} \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/data/StudIPParser.kt b/app/src/main/java/com/denizk0461/studip/data/StudIPParser.kt index adb576e..caef527 100644 --- a/app/src/main/java/com/denizk0461/studip/data/StudIPParser.kt +++ b/app/src/main/java/com/denizk0461/studip/data/StudIPParser.kt @@ -1,5 +1,7 @@ package com.denizk0461.studip.data +import android.app.Application +import com.denizk0461.studip.db.AppRepository import com.denizk0461.studip.model.StudIPEvent import org.jsoup.Jsoup import java.io.IOException @@ -7,7 +9,9 @@ import java.io.IOException /** * Parser class used for fetching and collecting scheduled events from a Stud.IP timetable. */ -class StudIPParser { +class StudIPParser(application: Application) { + + private val repo: AppRepository = AppRepository.getRepositoryInstance(application) /** * Parse a given HTML string. HTML must be of a Stud.IP timetable. @@ -84,7 +88,7 @@ class StudIPParser { // Construct the newly scraped Stud.IP event val event = StudIPEvent( - id = id, + eventId = id, title = parsedTitle, lecturer = parsedLecturers, room = entryInfo[1], @@ -102,7 +106,10 @@ class StudIPParser { } } + // Delete all previously saved events + repo.nukeEvents() + // After fetching has finished, save the list of new items into persistent storage - insert(newEvents) + repo.insertEvents(newEvents) } } \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/data/StwParser.kt b/app/src/main/java/com/denizk0461/studip/data/StwParser.kt index 4918626..e1b0ef1 100644 --- a/app/src/main/java/com/denizk0461/studip/data/StwParser.kt +++ b/app/src/main/java/com/denizk0461/studip/data/StwParser.kt @@ -1,5 +1,7 @@ package com.denizk0461.studip.data +import android.app.Application +import com.denizk0461.studip.db.AppRepository import com.denizk0461.studip.model.* import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -9,7 +11,7 @@ import org.jsoup.select.Elements * Parser class used for fetching and collecting canteen offers from the website of the * Studierendenwerk Bremen. */ -class StwParser { +class StwParser(application: Application) { // Unique primary key value for the date elements private var dateId = 0 @@ -26,6 +28,11 @@ class StwParser { // Determine whether there are offers for a given date private var dateHasItems = false + /** + * Static instance of the app's repository. + */ + private val repo: AppRepository = AppRepository.getRepositoryInstance(application) + /** * Parses through a list of canteen plans and saves them to persistent storage. * @@ -59,13 +66,13 @@ class StwParser { } // Delete all previous entries and start afresh - Dependencies.repo.nukeOffers() + repo.nukeOffers() // Fetch offers from the chosen canteen val (dates, canteens, categories, items) = parseFromPage(link) // Save everything all at once into the database - with (Dependencies.repo) { + with (repo) { insertDates(dates) insertCanteens(canteens) insertCategories(categories) diff --git a/app/src/main/java/com/denizk0461/studip/db/AppDAO.kt b/app/src/main/java/com/denizk0461/studip/db/AppDAO.kt index 3b54b69..cd4664e 100644 --- a/app/src/main/java/com/denizk0461/studip/db/AppDAO.kt +++ b/app/src/main/java/com/denizk0461/studip/db/AppDAO.kt @@ -18,7 +18,7 @@ interface AppDAO { * * @return all Stud.IP events exposed through a LiveData object */ - @get:Query("SELECT * FROM studipevents ORDER BY timeslotId, id") + @get:Query("SELECT * FROM studip_events ORDER BY timeslotId, eventId") val allEvents: LiveData> /** @@ -26,7 +26,7 @@ interface AppDAO { * * @return Stud.IP events for a certain day exposed through a LiveData object */ - @Query("SELECT * FROM studipevents WHERE day = :day ORDER BY timeslotId, id") + @Query("SELECT * FROM studip_events WHERE day = :day ORDER BY timeslotId, eventId") fun getEventsForDay(day: Int): LiveData> /** @@ -62,7 +62,7 @@ interface AppDAO { /** * Deletes all Stud.IP events from the database. */ - @Query("DELETE FROM studipevents") + @Query("DELETE FROM studip_events") fun nukeEvents() /* --- canteen offers --- */ diff --git a/app/src/main/java/com/denizk0461/studip/db/AppDatabase.kt b/app/src/main/java/com/denizk0461/studip/db/AppDatabase.kt index e0ac125..8e682cd 100644 --- a/app/src/main/java/com/denizk0461/studip/db/AppDatabase.kt +++ b/app/src/main/java/com/denizk0461/studip/db/AppDatabase.kt @@ -17,7 +17,7 @@ import com.denizk0461.studip.model.* OfferCategory::class, OfferItem::class, ], - version = 16, + version = 18, ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/denizk0461/studip/db/AppRepository.kt b/app/src/main/java/com/denizk0461/studip/db/AppRepository.kt index a7d88be..c8cc4ec 100644 --- a/app/src/main/java/com/denizk0461/studip/db/AppRepository.kt +++ b/app/src/main/java/com/denizk0461/studip/db/AppRepository.kt @@ -25,6 +25,27 @@ class AppRepository(app: Application) { // Blank dietary preference regular expression private val blankDietaryPrefs: String = DietaryPreferences.C_FALSE.toString().repeat(10) + companion object { + /** + * Reference to the app's repository. It is only instantiated once because it can only be + * accessed by [getRepositoryInstance]. + */ + private lateinit var repo: AppRepository + + /** + * Get a static instance of the repository. If none has been instantiated, one will be created + * and saved into [repo]. + * + * @return static instance of the repository + */ + fun getRepositoryInstance(app: Application): AppRepository { + if (!::repo.isInitialized) { + repo = AppRepository(app) + } + return repo + } + } + /** * Retrieves Stud.IP events for a specific day, ordered by their timeslots, then their IDs. * diff --git a/app/src/main/java/com/denizk0461/studip/fragment/EventPageFragment.kt b/app/src/main/java/com/denizk0461/studip/fragment/EventPageFragment.kt index a3bd2c4..3d9866b 100644 --- a/app/src/main/java/com/denizk0461/studip/fragment/EventPageFragment.kt +++ b/app/src/main/java/com/denizk0461/studip/fragment/EventPageFragment.kt @@ -66,7 +66,7 @@ class EventPageFragment( // Set up LiveData observer to refresh the view on update viewModel.getEventsForDay(currentDay).observe(viewLifecycleOwner) { events -> -// eventAdapter.setNewItems(events) + eventAdapter.setNewData(events) } } diff --git a/app/src/main/java/com/denizk0461/studip/model/StudIPEvent.kt b/app/src/main/java/com/denizk0461/studip/model/StudIPEvent.kt index 37c8fea..792bffe 100644 --- a/app/src/main/java/com/denizk0461/studip/model/StudIPEvent.kt +++ b/app/src/main/java/com/denizk0461/studip/model/StudIPEvent.kt @@ -6,7 +6,7 @@ import androidx.room.PrimaryKey /** * Entity for storing entries of the user's Stud.IP schedule. Registered in the app database. * - * @param id primary key that uniquely identifies the entry + * @param eventId primary key that uniquely identifies the entry * @param title title of the event * @param lecturer lecturer(s) organising the event * @param room room the event takes place in @@ -16,9 +16,9 @@ import androidx.room.PrimaryKey * @param timeslotId minute the event starts at - used for ordering * @param colour user-defined colour the event will be shown in - UNIMPLEMENTED */ -@Entity(tableName = "studipevents") +@Entity(tableName = "studip_events") data class StudIPEvent( - @PrimaryKey val id: Int, + @PrimaryKey val eventId: Int, val title: String, val lecturer: String, val room: String, @@ -45,7 +45,7 @@ data class StudIPEvent( other as StudIPEvent - if (id != other.id) return false + if (eventId != other.eventId) return false if (title != other.title) return false if (lecturer != other.lecturer) return false if (room != other.room) return false @@ -59,7 +59,7 @@ data class StudIPEvent( } override fun hashCode(): Int { - var result = id + var result = eventId result = 31 * result + title.hashCode() result = 31 * result + lecturer.hashCode() result = 31 * result + room.hashCode() diff --git a/app/src/main/java/com/denizk0461/studip/sheet/ScheduleUpdateSheet.kt b/app/src/main/java/com/denizk0461/studip/sheet/ScheduleUpdateSheet.kt index 2b1ec6b..8327611 100644 --- a/app/src/main/java/com/denizk0461/studip/sheet/ScheduleUpdateSheet.kt +++ b/app/src/main/java/com/denizk0461/studip/sheet/ScheduleUpdateSheet.kt @@ -133,7 +133,7 @@ class ScheduleUpdateSheet( onUpdate( // construct new StudIPEvent from the data the user may have edited StudIPEvent( - id = event.id, + eventId = event.eventId, title = binding.editTextTitle.text.toString(), lecturer = binding.editTextLecturers.text.toString(), room = binding.editTextRoom.text.toString(), diff --git a/app/src/main/java/com/denizk0461/studip/viewmodel/AppViewModel.kt b/app/src/main/java/com/denizk0461/studip/viewmodel/AppViewModel.kt index 5dc9ef6..62b2f12 100644 --- a/app/src/main/java/com/denizk0461/studip/viewmodel/AppViewModel.kt +++ b/app/src/main/java/com/denizk0461/studip/viewmodel/AppViewModel.kt @@ -3,7 +3,6 @@ package com.denizk0461.studip.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.denizk0461.studip.data.Dependencies import com.denizk0461.studip.db.AppRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -21,7 +20,7 @@ open class AppViewModel(app: Application) : AndroidViewModel(app) { /** * Reference to the app's repository for database transactions. */ - protected val repo: AppRepository = Dependencies.repo + protected val repo: AppRepository = AppRepository.getRepositoryInstance(app) /** * Execute a function asynchronously on the I/O thread. Be careful not to execute UI commands diff --git a/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt b/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt index 2a4ac1c..7a44fee 100644 --- a/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt +++ b/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt @@ -13,7 +13,7 @@ import com.denizk0461.studip.model.* class CanteenViewModel(app: Application) : AppViewModel(app) { // Instantiate a parser, should the user want to refresh the canteen offers - private val parser = StwParser() + private val parser = StwParser(app) /** * Retrieves all Stud.IP events. diff --git a/app/src/main/java/com/denizk0461/studip/viewmodel/FetcherViewModel.kt b/app/src/main/java/com/denizk0461/studip/viewmodel/FetcherViewModel.kt index b02d198..47f189b 100644 --- a/app/src/main/java/com/denizk0461/studip/viewmodel/FetcherViewModel.kt +++ b/app/src/main/java/com/denizk0461/studip/viewmodel/FetcherViewModel.kt @@ -1,7 +1,7 @@ package com.denizk0461.studip.viewmodel import android.app.Application -import com.denizk0461.studip.model.StudIPEvent +import com.denizk0461.studip.data.StudIPParser /** * View model for [com.denizk0461.studip.activity.FetcherActivity] @@ -10,15 +10,7 @@ import com.denizk0461.studip.model.StudIPEvent */ class FetcherViewModel(app: Application) : AppViewModel(app) { - /** - * Save a list of Stud.IP events to persistent storage asynchronously. - * - * @param events list of events to be saved - */ - fun insertEvents(events: List) { doAsync { repo.insertEvents(events) }} + private val parser: StudIPParser = StudIPParser(app) - /** - * Delete all Stud.IP events from the database asynchronously. - */ - fun nukeEvents() { doAsync { repo.nukeEvents() } } + fun parse(html: String) { doAsync { parser.parse(html, {}) }} } \ No newline at end of file