Added database persistence ability

This commit is contained in:
denizk0461
2023-04-08 17:30:05 +02:00
parent 9accebc68c
commit 3c65ccddb1
15 changed files with 176 additions and 68 deletions
+4
View File
@@ -1,6 +1,7 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
} }
android { android {
@@ -47,4 +48,7 @@ dependencies {
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.room:room-runtime:2.5.1"
kapt "androidx.room:room-compiler:2.5.1"
} }
@@ -11,7 +11,9 @@ import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.denizk0461.studip.R import com.denizk0461.studip.R
import com.denizk0461.studip.data.Dependencies
import com.denizk0461.studip.databinding.ActivityMainBinding import com.denizk0461.studip.databinding.ActivityMainBinding
import com.denizk0461.studip.db.EventRepository
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -22,9 +24,12 @@ class MainActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Dependencies.repo = EventRepository(application)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
val navController = findNavController(R.id.nav_host_fragment_content_main) val navController = findNavController(R.id.nav_host_fragment_content_main)
@@ -1,12 +1,10 @@
package com.denizk0461.studip.adapter package com.denizk0461.studip.adapter
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.denizk0461.studip.data.StudIPEvent import com.denizk0461.studip.data.StudIPEvent
import com.denizk0461.studip.databinding.ItemEventBinding import com.denizk0461.studip.databinding.ItemEventBinding
import com.denizk0461.studip.databinding.ItemEventPageBinding
class StudIPEventAdapter(private val events: List<StudIPEvent>) : RecyclerView.Adapter<StudIPEventAdapter.EventViewHolder>() { class StudIPEventAdapter(private val events: List<StudIPEvent>) : RecyclerView.Adapter<StudIPEventAdapter.EventViewHolder>() {
@@ -24,24 +22,13 @@ class StudIPEventAdapter(private val events: List<StudIPEvent>) : RecyclerView.A
override fun onBindViewHolder(holder: EventViewHolder, position: Int) { override fun onBindViewHolder(holder: EventViewHolder, position: Int) {
val currentItem = events[position] val currentItem = events[position]
// var itemCount = 0
// events.filter { it.day == position }.forEach { event ->
// val newView = ItemEventBinding.inflate(
// LayoutInflater.from(holder.binding.root.context),
// holder.binding.root,
// false
// ).also { item ->
// Log.d("HELLO", "generating ${event.title}...2")
// itemCount += 1
holder.binding.textTitle.text = currentItem.title holder.binding.textTitle.text = currentItem.title
holder.binding.textLecturers.text = currentItem.lecturer.joinToString(", ") holder.binding.textLecturers.text = currentItem.lecturer
holder.binding.textRoom.text = currentItem.room holder.binding.textRoom.text = currentItem.room
holder.binding.textTimeslot.text = currentItem.timeslot holder.binding.textTimeslot.text = currentItem.timeslot
// }
// holder.binding.pageLayout.addView(newView.root) // TODO inflate view saying "no events!" or sth
// }
// if (itemCount == 0) {
// // TODO inflate view saying "no events!" or sth
// }
} }
} }
@@ -1,13 +1,10 @@
package com.denizk0461.studip.adapter package com.denizk0461.studip.adapter
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.denizk0461.studip.data.StudIPEvent import com.denizk0461.studip.data.StudIPEvent
import com.denizk0461.studip.databinding.ItemEventBinding
import com.denizk0461.studip.databinding.ItemEventPageBinding import com.denizk0461.studip.databinding.ItemEventPageBinding
class StudIPEventPageAdapter(private val events: List<StudIPEvent>) : RecyclerView.Adapter<StudIPEventPageAdapter.EventPageViewHolder>() { class StudIPEventPageAdapter(private val events: List<StudIPEvent>) : RecyclerView.Adapter<StudIPEventPageAdapter.EventPageViewHolder>() {
@@ -29,26 +26,7 @@ class StudIPEventPageAdapter(private val events: List<StudIPEvent>) : RecyclerVi
holder.binding.pageRecyclerView.apply { holder.binding.pageRecyclerView.apply {
layoutManager = LinearLayoutManager(holder.binding.root.context, LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(holder.binding.root.context, LinearLayoutManager.VERTICAL, false)
adapter = StudIPEventAdapter(events.filter { it.day == position }) // TODO check if empty adapter = StudIPEventAdapter(events.filter { it.day == position }) // TODO check if empty
scheduleLayoutAnimation()
} }
// var itemCount = 0
// events.filter { it.day == position }.forEach { event ->
// val newView = ItemEventBinding.inflate(
// LayoutInflater.from(holder.binding.root.context),
// holder.binding.root,
// false
// ).also { item ->
// Log.d("HELLO", "generating ${event.title}...2")
// itemCount += 1
// item.textTitle.text = event.title
// item.textLecturers.text = event.lecturer.joinToString(", ")
// item.textRoom.text = event.room
// item.textTimeslot.text = event.timeslot
// }
// holder.binding.pageLayout.addView(newView.root)
// }
// if (itemCount == 0) {
// // TODO inflate view saying "no events!" or sth
// }
} }
} }
@@ -0,0 +1,7 @@
package com.denizk0461.studip.data
import com.denizk0461.studip.db.EventRepository
object Dependencies {
lateinit var repo: EventRepository
}
@@ -3,12 +3,12 @@ package com.denizk0461.studip.data
object DummyData { object DummyData {
val events: Array<StudIPEvent> = arrayOf( val events: Array<StudIPEvent> = arrayOf(
StudIPEvent("Literatures", arrayOf("Nittel"), "MZH 1380/1400", 0, 3, 1), StudIPEvent(1, "Literatures", "Nittel", "MZH 1380/1400", 0, 3, 1),
StudIPEvent("Linguistics", arrayOf("Du Bois"), "SFG 1020", 1, 1, 1), StudIPEvent(2, "Linguistics", "Du Bois", "SFG 1020", 1, 1, 1),
StudIPEvent("System Erde", arrayOf("Zolitschka", "Labuhn-Deroubaix"), "GW2 B1410", 1, 3, 1), StudIPEvent(3, "System Erde", "Zolitschka, Labuhn-Deroubaix", "GW2 B1410", 1, 3, 1),
StudIPEvent("Key Moments", arrayOf("Esders-Angermund"), "GW2 B2890", 2, 2, 1), StudIPEvent(4, "Key Moments", "Esders-Angermund", "GW2 B2890", 2, 2, 1),
StudIPEvent("ULS-1", arrayOf("Herrmann"), "GW2 B2890", 2, 3, 1), StudIPEvent(5, "ULS-1", "Herrmann", "GW2 B2890", 2, 3, 1),
StudIPEvent("Gesellschaft und Raum", arrayOf("Lossau", "Mossig"), "GW2 B1820", 3, 1, 1), StudIPEvent(6, "Gesellschaft und Raum", "Lossau, Mossig", "GW2 B1820", 3, 1, 1),
StudIPEvent("Gesellschaft und Raum2", arrayOf("Lossau", "Mossig"), "GW2 B1820", 4, 4, 2), StudIPEvent(7, "Gesellschaft und Raum2", "Lossau, Mossig", "GW2 B1820", 4, 4, 2),
) )
} }
@@ -1,18 +1,24 @@
package com.denizk0461.studip.data package com.denizk0461.studip.data
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(tableName = "events")
data class StudIPEvent( data class StudIPEvent(
@PrimaryKey val id: Int,
val title: String, // the name of the event val title: String, // the name of the event
val lecturer: Array<String>, // the lecturer(s) val lecturer: String, // the lecturer(s)
val room: String, // the room the event takes place in; assumes that the room doesn't change val room: String, // the room the event takes place in
val day: Int, // 0 = Monday, 1 = Tuesday, 2 = Wednesday, 3 = Thursday, 4 = Friday val day: Int, // 0 = Monday, 1 = Tuesday, 2 = Wednesday, 3 = Thursday, 4 = Friday
val timeslotStart: Int, // 0 = 08:15, 1 = 10:15, 2 = 12:15, 3 = 14:15, 4 = 16:15, 5 = 18:15 val timeslotStart: Int, // 0 = 08:15, 1 = 10:15, 2 = 12:15, 3 = 14:15, 4 = 16:15, 5 = 18:15
val timeslots: Int, // the amount of timeslots the course takes place over (1 timeslot = 90min) val timeslots: Int, // the amount of timeslots the course takes place over (1 timeslot = 90min)
) { ) {
private val timeSlotStartTimes: List<String> = listOf("08:15", "10:15", "12:15", "14:15", "16:15", "18:15") @Ignore private val timeSlotStartTimes: List<String> = listOf("08:15", "10:15", "12:15", "14:15", "16:15", "18:15")
private val timeSlotEndTimes: List<String> = listOf("09:45", "11:45", "13:45", "15:45", "17:45", "19:45") @Ignore private val timeSlotEndTimes: List<String> = listOf("09:45", "11:45", "13:45", "15:45", "17:45", "19:45")
val timeslot: String = @Ignore val timeslot: String =
"${timeSlotStartTimes[timeslotStart]} ${timeSlotEndTimes[timeslotStart + timeslots - 1]}" "${timeSlotStartTimes[timeslotStart]} ${timeSlotEndTimes[timeslotStart + timeslots - 1]}"
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -26,9 +32,18 @@ data class StudIPEvent(
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = title.hashCode() var result = id
result = 31 * result + lecturer.contentHashCode() result = 31 * result + title.hashCode()
result = 31 * result + lecturer.hashCode()
result = 31 * result + room.hashCode()
result = 31 * result + day
result = 31 * result + timeslotStart
result = 31 * result + timeslots
result = 31 * result + timeSlotStartTimes.hashCode()
result = 31 * result + timeSlotEndTimes.hashCode()
result = 31 * result + timeslot.hashCode()
return result return result
} }
} }
@@ -0,0 +1,20 @@
package com.denizk0461.studip.db
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.denizk0461.studip.data.StudIPEvent
@Dao
interface EventDAO {
@get:Query("SELECT * FROM events ORDER BY id")
val allEvents: LiveData<List<StudIPEvent>>
@Insert
fun insertEvent(event: StudIPEvent)
@Query("DELETE FROM events")
fun nukeEvents()
}
@@ -0,0 +1,30 @@
package com.denizk0461.studip.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.denizk0461.studip.data.StudIPEvent
@Database(entities = [StudIPEvent::class], version = 1)
abstract class EventDatabase : RoomDatabase() {
abstract fun dao(): EventDAO
companion object {
private var instance: EventDatabase? = null
fun getInstance(context: Context): EventDatabase {
if (instance == null) {
synchronized(EventDatabase::class) {
instance = Room.databaseBuilder(
context.applicationContext,
EventDatabase::class.java,
"event_db"
).build()
}
}
return instance!!
}
}
}
@@ -0,0 +1,15 @@
package com.denizk0461.studip.db
import android.app.Application
import androidx.lifecycle.LiveData
import com.denizk0461.studip.data.StudIPEvent
class EventRepository(app: Application) {
private val dao: EventDAO = EventDatabase.getInstance(app.applicationContext).dao()
val allEvents: LiveData<List<StudIPEvent>> = dao.allEvents
fun nukeEvents() { dao.nukeEvents() }
fun insertEvent(event: StudIPEvent) { dao.insertEvent(event) }
}
@@ -5,18 +5,21 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import com.denizk0461.studip.adapter.StudIPEventPageAdapter import com.denizk0461.studip.adapter.StudIPEventPageAdapter
import com.denizk0461.studip.data.DummyData import com.denizk0461.studip.data.DummyData
import com.denizk0461.studip.databinding.FragmentFirstBinding import com.denizk0461.studip.databinding.FragmentEventBinding
import com.denizk0461.studip.viewmodel.EventViewModel
/** /**
* A simple [Fragment] subclass as the default destination in the navigation. * A simple [Fragment] subclass as the default destination in the navigation.
*/ */
class FirstFragment : Fragment() { class EventFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null private var _binding: FragmentEventBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
@@ -24,19 +27,25 @@ class FirstFragment : Fragment() {
private lateinit var recyclerViewAdapter: StudIPEventPageAdapter private lateinit var recyclerViewAdapter: StudIPEventPageAdapter
private lateinit var recyclerViewLayoutManager: LinearLayoutManager private lateinit var recyclerViewLayoutManager: LinearLayoutManager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { private val viewModel: EventViewModel by viewModels()
_binding = FragmentFirstBinding.inflate(inflater, container, false)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentEventBinding.inflate(inflater, container, false)
return binding.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
recyclerViewAdapter = StudIPEventPageAdapter(DummyData.events.toList()) viewModel.allEvents.observe(viewLifecycleOwner) { events ->
binding.recyclerView.adapter = recyclerViewAdapter recyclerViewAdapter = StudIPEventPageAdapter(DummyData.events.toList())
recyclerViewLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) binding.recyclerView.adapter = recyclerViewAdapter
binding.recyclerView.layoutManager = recyclerViewLayoutManager recyclerViewLayoutManager =
PagerSnapHelper().attachToRecyclerView(binding.recyclerView) LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
binding.recyclerView.layoutManager = recyclerViewLayoutManager
PagerSnapHelper().attachToRecyclerView(binding.recyclerView)
binding.recyclerView.scheduleLayoutAnimation()
}
// binding.buttonFirst.setOnClickListener { // binding.buttonFirst.setOnClickListener {
// findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) // findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
@@ -0,0 +1,13 @@
package com.denizk0461.studip.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import com.denizk0461.studip.data.StudIPEvent
class EventViewModel(app: Application) : TemplateViewModel(app) {
val allEvents: LiveData<List<StudIPEvent>> = repo.allEvents
fun insertEvent(event: StudIPEvent) { repo.insertEvent(event) }
fun nukeEvents() { repo.nukeEvents() }
}
@@ -0,0 +1,25 @@
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.EventRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
open class TemplateViewModel(app: Application) : AndroidViewModel(app) {
protected val repo: EventRepository = Dependencies.repo
fun doAsync(function: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
function()
}
}
fun <T> returnBlocking(function: () -> T): T = runBlocking(Dispatchers.IO) {
function()
}
}
@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragment.FirstFragment"> tools:context=".fragment.EventFragment">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
+2 -2
View File
@@ -7,9 +7,9 @@
<fragment <fragment
android:id="@+id/FirstFragment" android:id="@+id/FirstFragment"
android:name="com.denizk0461.studip.fragment.FirstFragment" android:name="com.denizk0461.studip.fragment.EventFragment"
android:label="@string/first_fragment_label" android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_first"> tools:layout="@layout/fragment_event">
<action <action
android:id="@+id/action_FirstFragment_to_SecondFragment" android:id="@+id/action_FirstFragment_to_SecondFragment"