问题描述
我对 Kotlin
比较陌生,我正在为学校做一个项目。我已经被困在一些我现在无法弄清楚的事情上几天,要么是因为我不仅了解它是如何工作的,要么只是不知道要搜索什么。我正在构建一个用于简单预算跟踪的应用程序,并使用 Room DB
允许用户输入和存储他们的费用。我已经构建并运行了大部分应用程序,并拥有数据库、DAO
、Repository
和 viewmodel
。我已经成功编写了一个通过 Query
返回总和的 LiveData<Double>
。我设法通过 Toast
消息和 TextView
中的 MainActivity
显示此总和值(但电视不会在加载时更新,只有在启动首次修改数据库条目的活动)。
如果可能的话,我希望能够获取这个总和并将其存储在我为计算函数编写的单独类中,并在用户从数据库中输入或删除某些内容时更新它。或者最好让非活动类在调用类的相关函数时调用此总和。我似乎不明白如何从 MainActivity
之外的任何地方获取此值。我搜索和阅读的所有内容都有我认为我理解的代码部分,例如需要应用程序参数的 observeForever
,或者它们超出了我的脑海,因为它只是我无法理解的代码片段围绕它们如何组合在一起。
这是我目前所拥有的:
我的实体:
@Entity(tableName = "expenses")
data class Expenses (
@PrimaryKey(autoGenerate = true)
val id: Int,val expDesc: String,val expAmount: Double
)
我的 DAO:
@Dao
interface Dao {
@Insert(onConflict = OnConflictStrategy.IGnorE)
suspend fun addData(expenses: Expenses)
@Query("SELECT * FROM expenses ORDER BY id ASC")
fun readAllData(): LiveData<List<Expenses>>
@Query("SELECT SUM(expAmount) as expenseSum FROM expenses")
fun getExpenseSum(): LiveData<Double>
@Update
suspend fun updateExpense(expenses: Expenses)
@Delete
suspend fun deleteData(expenses: Expenses)
@Query("DELETE FROM expenses")
suspend fun deleteallData()
}
我的数据库:
@Database(entities = [Expenses::class],version = 1,exportSchema = false)
abstract class Database:RoomDatabase() {
abstract fun dao(): Dao
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): Dao?
}
companion object{
@Volatile
private var INSTANCE: com.example.finalproject.roomDB.Database? = null
fun getDatabase(context: Context): com.example.finalproject.roomDB.Database {
val instance = INSTANCE
if(instance != null){
return instance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,com.example.finalproject.roomDB.Database::class.java,"expenses").build()
INSTANCE = instance
return instance
}
}
}
}
我的存储库:
class Repository(private val dao: Dao) {
val readAllData: LiveData<List<Expenses>> = dao.readAllData()
val getExpenseSum: LiveData<Double> = dao.getExpenseSum()
suspend fun addData(expenses: Expenses){
dao.addData(expenses)
}
suspend fun updateData(expenses: Expenses){
dao.updateExpense(expenses)
}
suspend fun deleteData(expenses: Expenses){
dao.deleteData(expenses)
}
suspend fun deleteallData(){
dao.deleteallData()
}
}
我的视图模型:
class viewmodel(application: Application): Androidviewmodel(application) {
val readAllData: LiveData<List<Expenses>>
val getExpenseSum: LiveData<Double>
private val repository: Repository
init{
val dao = Database.getDatabase(application).dao()
repository = Repository(dao)
readAllData = repository.readAllData
getExpenseSum = repository.getExpenseSum
}
fun addData(expenses: Expenses){
viewmodelScope.launch(dispatchers.IO) {
repository.addData(expenses)
}
}
fun updateData(expenses: Expenses){
viewmodelScope.launch(dispatchers.IO) { repository.updateData(expenses) }
}
fun deleteData(expenses: Expenses){
viewmodelScope.launch(dispatchers.IO) { repository.deleteData(expenses) }
}
fun deleteallData(){
viewmodelScope.launch(dispatchers.IO) { repository.deleteallData() }
}
}
我当前与 MainActivity 相关的部分:
class MainActivity : AppCompatActivity() {
private lateinit var viewmodel: viewmodel
var sumTotal: Double = 0.0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val expenseViewButton = findViewById<Button>(R.id.expViewButton)
val incomeViewButton = findViewById<Button>(R.id.incViewButton)
val goalsViewButton = findViewById<Button>(R.id.goalsViewButton)
val expectedExpAmtView = findViewById<TextView>(R.id.expectedExpAmt)
viewmodel = viewmodelProvider.AndroidviewmodelFactory(application).create(viewmodel::class.java)
//This observer successfully casts the LiveData<Double> to a Double and updates whenever changed
val sumObserver = Observer<Double> { expSumDbl -> sumTotal = expSumDbl }
viewmodel.getExpenseSum.observe(this,sumObserver)
expectedExpAmtView.text = getString(R.string.monthly_expected_ExpAmt,sumTotal.toString())
expenseViewButton.setonClickListener{
val myIntent: Intent = Intent(this,ExpenseActivity::class.java)
startActivity(myIntent)
}
incomeViewButton.setonClickListener{
val myIntent: Intent = Intent(this,IncomeActivity::class.java)
startActivity(myIntent)
}
//This successfully displays the correct sum whenever the button is pressed
goalsViewButton.setonClickListener{
Toast.makeText(this@MainActivity,sumTotal.toString(),Toast.LENGTH_SHORT).show()
}
}
}
所以 sumTotal
是来自 MainActivity
的值,我想在不同的非活动类*中获得它,用于计算根本不会影响数据库,只会影响文本视图。我还希望正在更新的 TextView
始终保持最新状态,包括应用启动时间。如果有人能帮我弄清楚我做错了什么和/或需要做不同的事情,我将不胜感激。
*特别是目前,我有一个预算课程,可以处理收入、输入 editText 字段以及计算每月转化的金额。我想从数据库条目中取出总和,然后从预算类中的总和中减去它并返回结果。我可能想稍后用总和做其他(未决定的)事情,这就是我想将它存储在变量中的原因。
解决方法
sumTotal
中 MainActivity
的值实际上来自于 Repository.getExpenseSum
,因此与其将该变量共享给另一个类,不如从另一个类再次调用存储库方法可能更容易.实际上,我什至不建议在您的 Activity 中使用该变量 sumTotal
,最好仅依赖 ViewModel
,但这是一个不同的主题。
您的接口 Dao
包含方法:
fun getExpenseSum(): LiveData<Double>
返回一个 LiveData
。如果您想观察 LiveData
,您需要有一个 LifecycleOwner
,以便 LiveData
知道它应该何时开始和停止发出更新(或者您可以 observeForever
,但您需要知道自己什么时候停止观察)。
在您的预算课程中,假设它不是片段/活动,您将不会有 LifecycleOwner
,这就是为什么对您提出的一个建议是在 Dao
中创建另一种方法:
fun getExpenseSum(): Double
注意缺少 LiveData
。每当您同步调用该方法时,该方法都会返回一个双精度值,但它需要在后台线程上执行。您可以在预算类中调用该方法并在那里获取该值。
最后,我认为您不应该在某些“常规”类中调用这些数据库方法,您应该在创建预算类的实例时传递这些变量。当您使用标准 Android 类时,处理 LiveData
/background 线程要容易得多,只需将值传递给需要它的其他类,而不是让它们自己查询存储库。