【发布时间】:2019-12-22 14:31:45
【问题描述】:
首先我不得不说我对 Kotlin 还很陌生,在花了 5 天(35 多个小时)试图用谷歌搜索这个问题并尝试了无数不同的选项之后(关于堆栈溢出的类似问题、文档和教程) Google,GitHub 上的其他 Kotlin 项目,甚至使用我自己的服务器和数据库想知道问题是否与 ROOM 有关)我不得不放弃并寻求帮助,因为这个应用程序是我应该完成的任务几周。
应用说明(费用跟踪器):
- 当您打开应用程序时,您会看到 HomeFragment,其中显示了最近添加的费用。
- 有一个添加费用片段/选项卡,您可以在其中添加费用:写下费用名称和金额,从微调器中选择类别,从日期选择器中选择日期(默认:今天)。
- 有一个总计片段/选项卡,您可以在其中查看费用的统计信息/数据。我有一个类别微调器和时间选项微调器(今天,本月,今年,所有时间),当用户单击按钮时,我正在从所选选项构建查询,并希望根据用户的偏好显示数据下面是我的 RecyclerView。
- 在 RecyclerView 上方,您可以看到您的费用、收入和总计(总计 = 收入 - 费用,一旦我弄清楚了这部分,使用 SELECT SUM 查询来获取收入和费用),并且 RV 应该只是一个列表查询结果,用户可以通过向左滑动来删除单个费用(非常基本的东西,已经在 HomeFragment 上工作,其中 RV 在静态查询中显示得很好)。
我觉得我已经尝试了所有的东西——尤其是 Transformations.switchMap,因为许多结果似乎都指向了这一点,但我没有取得任何进展。我已经浏览了 GitHub 上的几十个应用程序,看看他们是如何工作的,试图在我的中实现逻辑,但是即使我一直设法调整代码以使我没有错误,我的 RecyclerView 上仍然没有显示任何内容.
以下是我认为与此问题相关的类中的 sn-ps(按照从最相关到有些相关的顺序,省略了某些代码部分以不完全淹没这篇文章):
TotalsFragment:
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.expensetracker.R
import com.example.expensetracker.model.Category
import com.example.expensetracker.model.Expense
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_totals.*
import java.util.*
import kotlin.collections.ArrayList
class TotalsFragment : Fragment() {
private val totals: MutableList<Expense> = ArrayList()
private val totalAdapter = ExpenseAdapterTotals(totals)
private lateinit var viewModel: TotalsViewModel
//
// Bunch of variables omitted
//
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Initialize the ViewModel
viewModel = ViewModelProviders.of(activity as AppCompatActivity).get(TotalsViewModel::class.java)
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_totals, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateUI()
initViewModel()
initViews()
initCategorySpinner()
initTimeSpinner()
// For getting data and updating the UI after the button is clicked
btn_show.setOnClickListener {
updateRvData()
updateTotals()
updateUI()
}
}
private fun initViewModel(){
viewModel = ViewModelProviders.of(this).get(TotalsViewModel::class.java)
viewModel.totals.observe(this, Observer {
if (totals.isNotEmpty()) {
totals.clear()
}
totals.addAll(it!!)
totalAdapter.notifyDataSetChanged()
})
}
private fun initViews(){
createItemTouchHelper().attachToRecyclerView(rv_expenses_totals)
rv_expenses_totals.apply {
layoutManager = LinearLayoutManager(activity)
rv_expenses_totals.adapter = totalAdapter
rv_expenses_totals.addItemDecoration(DividerItemDecoration(this.context, DividerItemDecoration.VERTICAL))
}
}
// Code omitted
向前发送查询的部分:
viewModel.getTotals(queryString)
TotalsViewModel:
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.example.expensetracker.database.ExpenseRepository
import com.example.expensetracker.model.Expense
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TotalsViewModel(application: Application) : AndroidViewModel(application) {
private val ioScope = CoroutineScope(Dispatchers.IO)
private val expenseRepository = ExpenseRepository(application.applicationContext)
var query = MutableLiveData<String>()
val totals: LiveData<List<Expense>> = Transformations.switchMap(query, ::temp)
private fun temp(query: String) = expenseRepository.getTotals(query)
fun getTotals(queryString: String) = apply { query.value = queryString }
fun insertExpense(expense: Expense) {
ioScope.launch {
expenseRepository.insertExpense(expense)
}
}
fun deleteExpense(expense: Expense) {
ioScope.launch {
expenseRepository.deleteExpense(expense)
}
}
}
ExpenseDao:
@Dao
interface ExpenseDao {
// sort by order they were added, newest on top
@Query("SELECT * FROM expense_table ORDER BY id DESC LIMIT 15")
fun getExpensesMain(): LiveData<List<Expense>>
// get data for totals
@Query("SELECT * FROM expense_table WHERE :queryString")
fun getTotals(queryString: String): LiveData<List<Expense>>
// Rest of the queries omitted
ExpenseRepository:
class ExpenseRepository(context: Context) {
private var expenseDao: ExpenseDao
init {
val expenseRoomDatabase = ExpenseRoomDatabase.getDatabase(context)
expenseDao = expenseRoomDatabase!!.expenseDao()
}
fun getExpensesMain(): LiveData<List<Expense>> {
return expenseDao.getExpensesMain()
}
fun getTotals(queryString: String): LiveData<List<Expense>> {
return expenseDao.getTotals(queryString)
}
// Code omitted
ExpenseRoomDatabase:
@Database(entities = [Expense::class], version = 1, exportSchema = false)
abstract class ExpenseRoomDatabase : RoomDatabase() {
abstract fun expenseDao(): ExpenseDao
companion object {
private const val DATABASE_NAME = "EXPENSE_DATABASE"
@Volatile
private var expenseRoomDatabaseInstance: ExpenseRoomDatabase? = null
fun getDatabase(context: Context): ExpenseRoomDatabase? {
if (expenseRoomDatabaseInstance == null) {
synchronized(ExpenseRoomDatabase::class.java) {
if (expenseRoomDatabaseInstance == null) {
expenseRoomDatabaseInstance = Room.databaseBuilder(
context.applicationContext,
ExpenseRoomDatabase::class.java, DATABASE_NAME
).build()
}
}
}
return expenseRoomDatabaseInstance
}
}
}
ExpenseAdapterTotals:
class ExpenseAdapterTotals(private val totals: MutableList<Expense>) : RecyclerView.Adapter<ExpenseAdapterTotals.ViewHolder>() {
lateinit var context: Context
override fun getItemCount(): Int {
return totals.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_expense_totals, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(totals[position])
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(totals: Expense) {
itemView.tv_expense_totals.text = totals.expense
itemView.tv_category_totals.text = totals.category
itemView.tv_date_totals.text = totals.date
itemView.tv_total_totals.text = totals.total.toString()
}
}
}
我的应用 build.gradle 中有以下依赖项:
//Navigation
implementation "androidx.navigation:navigation-fragment-ktx:2.0.0"
implementation "androidx.navigation:navigation-ui-ktx:2.0.0"
// ViewModel and LiveData
def lifecycle_version = "2.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// Room.
def room_version = "2.1.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
....
所以,这段代码是我最近的尝试,但它已经改变了好几次。我没有收到任何错误消息,但也没有显示任何内容。
简而言之,我的目标:当我单击按钮 (btn_show) 时,它应该创建查询字符串(它会这样做),并且该片段中的 RecyclerView 应该更新以显示所需的结果(它没有)。我认为问题出在 ViewModel 和 Fragment 之间,但就像我说的,我还是个初学者,这是我第一次真正地在自己的应用程序上工作。
非常感谢您提供的任何帮助和提示,如果我遗漏了您想知道的任何内容,请随时询问。
【问题讨论】:
-
有趣的问题。我在此处发布此评论是因为我想在我靠近 PC 时从“所有操作”选项卡中找到此评论。
标签: android kotlin android-recyclerview android-room android-viewmodel