Archived
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,7 @@ class StudIPEventItemAdapter(
|
||||
) : RecyclerView.Adapter<StudIPEventItemAdapter.EventViewHolder>() {
|
||||
|
||||
/**
|
||||
* List of all events (still not filtered by day at this point)
|
||||
* List of all events.
|
||||
*/
|
||||
private var events: MutableList<StudIPEvent> = mutableListOf()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<List<StudIPEvent>>
|
||||
|
||||
/**
|
||||
@@ -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<List<StudIPEvent>>
|
||||
|
||||
/**
|
||||
@@ -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 --- */
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.denizk0461.studip.model.*
|
||||
OfferCategory::class,
|
||||
OfferItem::class,
|
||||
],
|
||||
version = 16,
|
||||
version = 18,
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<StudIPEvent>) { 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, {}) }}
|
||||
}
|
||||
Reference in New Issue
Block a user