【问题标题】:Unable to get the ViewModel to show data after querying the ROOM database with parameters (Kotlin)使用参数查询 ROOM 数据库后无法让 ViewModel 显示数据(Kotlin)
【发布时间】: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


【解决方案1】:

我注意到了一些事情: 在您的总计片段中,为什么要在 onCreate 和 onViewCreated 中初始化 viewmodel 两次?

此外,您没有将总计值提交到适配器中。 totals.addAll(it!!) 这只是将它们添加到您在 totalFragment 中声明的列表中(您根本不需要它,因为您首先从 viewmodel 获取所有总数。)

【讨论】:

  • 我删除了从 onCreate 初始化的视图模型和观察部分的 totals.addAll(it!!)。第一个可能是来自另一次尝试的结果,而 totals.addAll(it!!) 是较早的 totals.addAll(totals),这是我之前在练习应用程序中使用过的。但是,是的,它们似乎没有太大影响......
【解决方案2】:

将您的ExpenseAdapterTotals: RecyclerView.Adapter&lt; 替换为ExpenseAdapterTotals: ListAdapter&lt;

然后,删除任何显示 MutableList 的内容,或者至少将其重命名为 List

现在您看到您不需要clear()addAll()。您只需在 ListAdapter 上调用 submitList() 即可。

var query = MutableLiveData<String>()

把它变成val,这样你就不会不小心把它搞砸了。

viewModel.totals.observe(this, Observer {

如果您在onViewCreated 中设置此观察者,则应为viewLifecycleOwner


但是由于RecyclerView没有显示,我实际上认为这可能是布局参数不正确的问题,例如RecyclerView的wrap_content高度,现在由于setHasFixedSize(true)它没有更新它的高度。

【讨论】:

  • 您好,很抱歉打扰您,但我在切换到 ListAdapter 时遇到了一些问题。我以前从未使用过它,也找不到让它与我的代码一起工作的方法(我需要提供的参数有问题,也对传递查询字符串感到困惑)。 RecyclerView 确实具有wrap_content 高度,并且它与 HomeFragment 上的高度相同,其中 RV 工作得很好(相当基本的全选查询,在我在另一个片段上添加费用并返回 HomeFragment 后更新内容)。我不能在这种情况下包装内容吗?
  • ListAdapter 的诀窍是,你在更改时执行class ExpenseAdapterTotals()submitList(totals) 而不是class ExpenseAdapterTotals(private val totals: MutableList&lt;Expense&gt;)wrap_content 只要你放setHasFixedSize(false) 就可以工作,但你可能会失去视图回收(特别是如果你把 RecyclerView 放在NestedScrollView 中)。通常对此的解决方案是使用多种视图类型并将该屏幕的多个视图组合到 RecyclerView 本身中。例如,请参阅github.com/lisawray/groupie
猜你喜欢
  • 2022-06-29
  • 2021-01-06
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 2022-12-08
  • 1970-01-01
  • 1970-01-01
  • 2019-02-13
相关资源
最近更新 更多