将数据库使用Jetpack Room重构

This commit is contained in:
Super12138 2024-01-07 14:32:25 +08:00
parent 49b50ef4ca
commit 60f99bb8f9
14 changed files with 237 additions and 197 deletions

View file

@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
}
val baseVersionName = "1.0.0"
@ -60,6 +61,11 @@ dependencies {
implementation("androidx.preference:preference-ktx:1.2.1")
// Material Design
implementation("com.google.android.material:material:1.12.0-alpha02")
// Room
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
annotationProcessor("androidx.room:room-compiler:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
// Test
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

View file

@ -3,13 +3,19 @@ package cn.super12138.todo
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import cn.super12138.todo.logic.dao.ToDoRoomDB
import cn.super12138.todo.views.crash.CrashHandler
import com.google.android.material.color.DynamicColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ToDoApplication : Application() {
private val database by lazy { ToDoRoomDB.getDatabase(this) }
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
lateinit var db: ToDoRoomDB
}
override fun onCreate() {
@ -19,5 +25,7 @@ class ToDoApplication : Application() {
val crashHandler = CrashHandler(this)
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
db = database
}
}

View file

@ -1,21 +1,88 @@
package cn.super12138.todo.logic
import android.content.ContentValues
import android.content.Context
import cn.super12138.todo.logic.database.DBHelper
import cn.super12138.todo.ToDoApplication
import cn.super12138.todo.logic.dao.ToDoRoom
import cn.super12138.todo.logic.database.SPHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object Repository {
fun getCompleteTotalCount() = DBHelper.getCompleteTotalCount()
fun insertData(data: ContentValues) = DBHelper.insertData(data)
fun deleteData(deleteAll: Boolean, uuid: String?) = DBHelper.deleteData(deleteAll, uuid)
fun updateData(uuid: String, newData: ContentValues) = DBHelper.updateData(uuid, newData)
fun getAllData() = DBHelper.getAllData()
/**
* 获取应用设置里的数据
*/
fun getPreferenceString(context: Context, key: String, defaultValue: String) =
SPHelper.getPreferenceString(context, key, defaultValue)
// Room
private val db get() = ToDoApplication.db
val todoDao = db.toDoRoomDao()
/**
* @param toDoRoom 要插入的数据
*/
suspend fun insert(toDoRoom: ToDoRoom) {
withContext(Dispatchers.IO) {
todoDao.insert(toDoRoom)
}
}
/**
* 获取全部未完成的待办
* @return List<ToDoRoom>
*/
suspend fun getAllUncomplete(): List<ToDoRoom> {
return withContext(Dispatchers.IO) {
todoDao.getAllUnfinished()
}
}
/**
* 获取全部已完成的待办
* @return List<ToDoRoom>
*/
suspend fun getAllComplete(): List<ToDoRoom> {
return withContext(Dispatchers.IO) {
todoDao.getAllComplete()
}
}
/**
* 获取全部待办
* @return List<ToDoRoom>
*/
suspend fun getAll(): List<ToDoRoom> {
return withContext(Dispatchers.IO) {
todoDao.getAll()
}
}
/**
* 根据待办的UUID删除指定待办
* @param uuid 待办的UUID
*/
suspend fun deleteByUUID(uuid: String) {
withContext(Dispatchers.IO) {
todoDao.deleteByUUID(uuid)
}
}
/**
* 删除全部代办
*/
suspend fun deleteAll() {
withContext(Dispatchers.IO) {
todoDao.deleteAll()
}
}
/**
* 根据代办的UUID来把待办状态更新为已完成
* @param uuid 待办的UUID
*/
suspend fun updateStateByUUID(uuid: String) {
withContext(Dispatchers.IO) {
todoDao.updateStateByUUID(uuid)
}
}
}

View file

@ -0,0 +1,19 @@
package cn.super12138.todo.logic.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* @param uuid String 待办的uuid
* @param state Int 待办的完成状态0表示未完成1表示完成
* @param subject String 待办的学科
* @param context String 待办的内容
*/
@Entity(tableName = "todo")
data class ToDoRoom(
@PrimaryKey @ColumnInfo(name = "uuid") val uuid: String,
@ColumnInfo(name = "state") val state: Int,
@ColumnInfo(name = "subject") val subject: String,
@ColumnInfo(name = "context") val context: String
)

View file

@ -0,0 +1,27 @@
package cn.super12138.todo.logic.dao
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [ToDoRoom::class], version = 1)
abstract class ToDoRoomDB : RoomDatabase() {
abstract fun toDoRoomDao(): ToDoRoomDao
companion object {
@Volatile
private var INSTANCE: ToDoRoomDB? = null
fun getDatabase(context: Context): ToDoRoomDB {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ToDoRoomDB::class.java,
"todo"
).build()
INSTANCE = instance
instance
}
}
}
}

View file

@ -0,0 +1,29 @@
package cn.super12138.todo.logic.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
@Dao
interface ToDoRoomDao {
@Insert
suspend fun insert(toDoRoom: ToDoRoom)
@Query("SELECT * FROM todo")
suspend fun getAll(): List<ToDoRoom>
@Query("SELECT * FROM todo WHERE state = 0")
suspend fun getAllUnfinished(): List<ToDoRoom>
@Query("SELECT * FROM todo WHERE state = 1")
suspend fun getAllComplete(): List<ToDoRoom>
@Query("DELETE FROM todo")
suspend fun deleteAll()
@Query("DELETE FROM todo WHERE uuid = :uuid")
suspend fun deleteByUUID(uuid: String)
@Query("UPDATE todo SET state = 1 WHERE uuid = :uuid")
suspend fun updateStateByUUID(uuid: String)
}

View file

@ -1,109 +0,0 @@
package cn.super12138.todo.logic.database
import android.annotation.SuppressLint
import android.content.ContentValues
import cn.super12138.todo.ToDoApplication
import cn.super12138.todo.constant.Constants
import cn.super12138.todo.logic.model.Progress
import cn.super12138.todo.logic.model.ToDo
import cn.super12138.todo.logic.model.ToDoDatabase
object DBHelper {
fun insertData(data: ContentValues) {
ToDoDatabase(
ToDoApplication.context,
Constants.DB_NAME,
Constants.DB_VERSION
).writableDatabase.use { db ->
db.insert(Constants.TABLE_NAME, null, data)
}
}
fun deleteData(delAll: Boolean, uuid: String?) {
ToDoDatabase(
ToDoApplication.context,
Constants.DB_NAME,
Constants.DB_VERSION
).writableDatabase.use { db ->
if (delAll) {
db.delete(Constants.TABLE_NAME, null, null)
} else {
if (uuid == null) {
throw RuntimeException("uuid cannot be null")
}
db.delete(Constants.TABLE_NAME, "uuid = ?", arrayOf(uuid))
}
}
}
fun updateData(uuid: String, newData: ContentValues) {
val dbHelper = ToDoDatabase(
ToDoApplication.context,
Constants.DB_NAME,
Constants.DB_VERSION
).writableDatabase
dbHelper.update(Constants.TABLE_NAME, newData, "uuid = ?", arrayOf(uuid))
}
@SuppressLint("Range")
fun getCompleteTotalCount(): Progress {
var total = 0
var complete = 0
ToDoDatabase(
ToDoApplication.context,
Constants.DB_NAME,
Constants.DB_VERSION
).writableDatabase.use { db ->
db.query(Constants.TABLE_NAME, null, null, null, null, null, null, null).use { cursor ->
if (cursor.moveToFirst()) {
do {
val state = cursor.getString(cursor.getColumnIndex("state"))
if (state.toInt() == 1) {
complete += 1
}
total += 1
} while (cursor.moveToNext())
cursor.close()
}
}
}
return Progress(complete, total)
}
@SuppressLint("Range")
fun getAllData(): MutableList<ToDo> {
val todoList = mutableListOf<ToDo>()
ToDoDatabase(
ToDoApplication.context,
Constants.DB_NAME,
Constants.DB_VERSION
).writableDatabase.query(
Constants.TABLE_NAME,
null,
null,
null,
null,
null,
null,
null
).use { cursor ->
if (cursor.moveToFirst()) {
do {
val uuid = cursor.getString(cursor.getColumnIndex("uuid"))
val subject = cursor.getString(cursor.getColumnIndex("subject"))
val state = cursor.getString(cursor.getColumnIndex("state"))
val todoContext = cursor.getString(cursor.getColumnIndex("context"))
if (state.toInt() != 1) {
todoList.add(ToDo(uuid, todoContext, subject))
}
} while (cursor.moveToNext())
cursor.close()
}
}
return todoList
}
}

View file

@ -1,3 +0,0 @@
package cn.super12138.todo.logic.model
data class Progress(val complete: Int, val total: Int)

View file

@ -1,28 +0,0 @@
package cn.super12138.todo.logic.model
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class ToDoDatabase(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
/*
* @param uuid: String 待办的uuid
* @param state: Int 待办的完成状态0表示未完成1表示完成
* @param subject: String 待办的学科
* @param context: String 待办的内容
*/
private val createToDo = "create table ToDo (" +
"uuid text primary key," +
"state integer," +
"subject text," +
"context text)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createToDo)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}

View file

@ -3,6 +3,7 @@ package cn.super12138.todo.views.crash
import android.content.Context
import android.content.Intent
import android.os.Process
import kotlin.system.exitProcess
class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
@ -18,11 +19,11 @@ class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandl
}
context.startActivity(intent)
// 杀掉崩溃的应用程序进程
Process.killProcess(Process.myPid())
System.exit(10)
// 传递异常给默认的异常处理器
defaultUEH?.uncaughtException(thread, ex)
// 杀掉崩溃的应用程序进程
Process.killProcess(Process.myPid())
exitProcess(10)
}
}

View file

@ -2,7 +2,9 @@ package cn.super12138.todo.views.progress
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cn.super12138.todo.logic.Repository
import kotlinx.coroutines.launch
class ProgressFragmentViewModel : ViewModel() {
val totalCount: MutableLiveData<Int> = MutableLiveData()
@ -10,13 +12,14 @@ class ProgressFragmentViewModel : ViewModel() {
val progress: MutableLiveData<Int> = MutableLiveData()
fun updateProgress() {
val progressData = Repository.getCompleteTotalCount()
val total = progressData.total
val complete = progressData.complete
val calcProgress = (complete.toDouble() / total.toDouble()) * 100
viewModelScope.launch {
val total = Repository.getAll().size
val complete = Repository.getAllComplete().size
progress.value = calcProgress.toInt()
totalCount.value = total
completeCount.value = complete
val calcProgress = (complete.toDouble() / total.toDouble()) * 100
progress.postValue(calcProgress.toInt())
completeCount.postValue(complete)
totalCount.postValue(total)
}
}
}

View file

@ -1,6 +1,5 @@
package cn.super12138.todo.views.todo
import android.content.ContentValues
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -11,9 +10,13 @@ import androidx.lifecycle.ViewModelStoreOwner
import androidx.recyclerview.widget.RecyclerView
import cn.super12138.todo.R
import cn.super12138.todo.logic.Repository
import cn.super12138.todo.logic.dao.ToDoRoom
import cn.super12138.todo.logic.model.ToDo
import cn.super12138.todo.views.progress.ProgressFragmentViewModel
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
class ToDoAdapter(val todoList: MutableList<ToDo>, val viewModelStoreOwner: ViewModelStoreOwner) :
RecyclerView.Adapter<ToDoAdapter.ViewHolder>() {
@ -47,12 +50,10 @@ class ToDoAdapter(val todoList: MutableList<ToDo>, val viewModelStoreOwner: View
notifyItemRemoved(position)
notifyItemRangeChanged(position, todoList.size)
val newState = ContentValues().apply {
put("state", true)
GlobalScope.launch {
Repository.updateStateByUUID(todo.uuid)
progressViewModel.updateProgress()
}
Repository.updateData(todo.uuid, newState)
progressViewModel.updateProgress()
// 设置空项目提示可见性
if (todoList.isEmpty()) {
@ -73,16 +74,11 @@ class ToDoAdapter(val todoList: MutableList<ToDo>, val viewModelStoreOwner: View
notifyItemRemoved(position)
notifyItemRangeChanged(position, todoList.size)
val tempTaskInfo = ContentValues().apply {
put("uuid", todo.uuid)
put("state", false)
put("subject", todo.subject)
put("context", todo.context)
GlobalScope.launch {
Repository.deleteByUUID(todo.uuid)
progressViewModel.updateProgress()
}
Repository.deleteData(false, todo.uuid)
progressViewModel.updateProgress()
// 设置空项目提示可见性
if (todoList.isEmpty()) {
@ -99,8 +95,18 @@ class ToDoAdapter(val todoList: MutableList<ToDo>, val viewModelStoreOwner: View
todoList.add(ToDo(todo.uuid, todo.context, todo.subject))
todoViewModel.refreshData.value = 1
Repository.insertData(tempTaskInfo)
progressViewModel.updateProgress()
GlobalScope.launch {
Repository.insert(
ToDoRoom(
todo.uuid,
0,
todo.subject,
todo.context
)
)
progressViewModel.updateProgress()
}
}
.show()
}

View file

@ -1,6 +1,5 @@
package cn.super12138.todo.views.todo
import android.content.ContentValues
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -8,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import cn.super12138.todo.R
@ -15,9 +15,11 @@ import cn.super12138.todo.ToDoApplication
import cn.super12138.todo.databinding.DialogAddTodoBinding
import cn.super12138.todo.databinding.FragmentTodoBinding
import cn.super12138.todo.logic.Repository
import cn.super12138.todo.logic.dao.ToDoRoom
import cn.super12138.todo.logic.model.ToDo
import cn.super12138.todo.views.progress.ProgressFragmentViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import java.util.UUID
class ToDoFragment : Fragment() {
@ -38,11 +40,6 @@ class ToDoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val todos = Repository.getAllData()
for (todo in todos) {
todoList.add(ToDo(todo.uuid, todo.context, todo.subject))
}
/*ViewCompat.setOnApplyWindowInsetsListener(binding.addItem) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@ -63,6 +60,20 @@ class ToDoFragment : Fragment() {
val todoViewModel =
ViewModelProvider(requireActivity()).get(ToDoFragmentViewModel::class.java)
lifecycleScope.launch {
val todos = Repository.getAllUncomplete()
var count = 0
for (todo in todos) {
todoList.add(ToDo(todo.uuid, todo.context, todo.subject))
count++
}
if (count == 0) {
todoViewModel.emptyTipVis.value = View.VISIBLE
} else {
todoViewModel.emptyTipVis.value = View.GONE
}
}
if (todoList.size == 0) {
todoViewModel.emptyTipVis.value = View.VISIBLE
}
@ -99,18 +110,19 @@ class ToDoFragment : Fragment() {
ToDo(randomUUID, todoContext, todoSubject)
)
// 添加到数据库
val todoData = ContentValues().apply {
put("uuid", randomUUID)
put("state", 0)
put("subject", todoSubject)
put("context", todoContext)
lifecycleScope.launch {
Repository.insert(
ToDoRoom(
randomUUID,
0,
todoSubject,
todoContext
)
)
progressViewModel.updateProgress()
}
Repository.insertData(todoData)
binding.todoList.adapter?.notifyItemInserted(todoList.size + 1)
progressViewModel.updateProgress()
}
.setNegativeButton(R.string.cancel, null)
.show()
@ -124,11 +136,12 @@ class ToDoFragment : Fragment() {
.setMessage(R.string.delete_confirm)
.setPositiveButton(R.string.ok) { dialog, which ->
todoList.clear()
Repository.deleteData(true, null)
lifecycleScope.launch {
Repository.deleteAll()
progressViewModel.updateProgress()
}
binding.todoList.adapter?.notifyItemRangeRemoved(0, todoList.size + 1)
progressViewModel.updateProgress()
todoViewModel.emptyTipVis.value = View.VISIBLE
}
.setNegativeButton(R.string.cancel, null)

View file

@ -2,4 +2,5 @@
plugins {
id("com.android.application") version "8.2.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.16" apply false
}