【问题标题】:Room database view with RecyclerView使用 RecyclerView 的房间数据库视图
【发布时间】:2019-03-02 14:40:57
【问题描述】:

我是 Room 和 Kotlin 的新手,我正在尝试构建一个简单的 Room 数据库并将其呈现在 ViewPager 中的 RecyclerView 中。

Activity包含一个ViewPager对象,那个ViewPager包含一个Fragment,那个Fragment包含一个RecyclerView: Activity --> ViewPager --> Fragment --> RecyclerView

我遇到的问题是当我尝试接收数据库时(插入后)我得到 null。

代码:

@Entity(tableName = "Guests_table")
data class Guest(@NonNull @ColumnInfo (name = "Name") var name: String,
             @ColumnInfo (name = "Phone number") var phoneNumber: String,
             @ColumnInfo (name = "Coming") var coming: Boolean = false,
             @ColumnInfo (name = "Participants") var participants: Int)
{
@PrimaryKey (autoGenerate = true)
var id: Long = 0

init
{
    phoneNumber = PhoneNumberUtils.formatNumber(phoneNumber, Locale.getDefault().country)
}
}

道:

@Dao
interface GuestsDAO
{
    @Insert
    fun insert(guest: Guest)

    @Delete
    fun delete(guest: Guest)

    @Query ("DELETE FROM Guests_table")
    fun deleteAll()

    @Query ("SELECT * FROM Guests_table ORDER BY name ASC")
    fun getAllGuests(): List<Guest>
}

数据库:

@Database(entities = [Guest::class], version = 1)
abstract class InviterRoomDatabase: RoomDatabase()
{
    abstract fun guestsDao(): GuestsDAO

    companion object
    {
        private var INSTANCE: InviterRoomDatabase ?= null

        fun getDatabase(context: Context): InviterRoomDatabase?
        {
            if (INSTANCE == null)
            {
                synchronized(InviterRoomDatabase::class)
                {
                    INSTANCE = Room.databaseBuilder(context.applicationContext,InviterRoomDatabase::class.java,"Guests.db").build()
                }
            }

            return INSTANCE
        }

        fun destroyInstance()
        {
            INSTANCE = null
        }
    }
}

活动:

class EventActivity : AppCompatActivity()
{
    private lateinit var viewPager: ViewPager

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_event)

        viewPager = findViewById(R.id.guests_view_pager)
        val pagerAdapter =  EventPagerAdapter(supportFragmentManager)
        viewPager.adapter = pagerAdapter
    }
}

适配器:

class EventPagerAdapter(fragmentManager: FragmentManager): FragmentPagerAdapter(fragmentManager)
{

    override fun getCount(): Int
    {
        return 1
    }

    override fun getItem(position: Int): Fragment
    {
        return GuestListFragment.newInstance()
    }
}

片段:

class GuestListFragment : Fragment()
{
    private var guestsDataBase: InviterRoomDatabase? = null
    private lateinit var dbWorkerThread: DBWorkerThread

    companion object
    {
        fun newInstance(): GuestListFragment
        {
            val fragment = GuestListFragment()
            return fragment
        }
    }


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? 
    {

        dbWorkerThread = DBWorkerThread("workerThread")
        dbWorkerThread.start()
        guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
        insertGuestToDB(Guest("Joe","052352332",false,0))

        var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
        var rv: RecyclerView = rootView.findViewById(R.id.guests_list_recycler_view)
        rv.layoutManager = LinearLayoutManager(context)

        var d = getAllDataFromDB()
        rv.adapter = GuestsRecycleViewAdapter.newInstance(d)

        return rootView
    }


    private fun insertGuestToDB(guest: Guest)
    {
        val task = Runnable {guestsDataBase?.guestsDao()?.insert(guest)}
        dbWorkerThread.postTask(task)
    }

    private fun getAllDataFromDB(): List<Guest>
    {
        var data: List<Guest> = emptyList()
        val task = Runnable { data = guestsDataBase?.guestsDao()?.getAllGuests()!!}

        dbWorkerThread.postTask(task)

        return data
    }
}

【问题讨论】:

  • 你确定数据库是空的吗?当您在插入和获取函数中使用线程时,您可能会得到一个空值,因为当您尝试从数据库中获取数据时尚未添加数据..
  • 我尝试单独插入并获取所有数据,但我仍然得到空列表

标签: android database kotlin android-room


【解决方案1】:

您需要了解多线程的概念,才能了解您的代码为何不起作用。简而言之,您得到一个空列表,因为您试图在主线程中获取列表,而您在工作线程上运行的插入和查询尚未完成。

我把注释放在代码中说明执行顺序。评论中的数字是完成的顺序:


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? 
{
    // [1] onCreateView starts on main thread.
    dbWorkerThread = DBWorkerThread("workerThread")
    dbWorkerThread.start()
    guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
     // [2] calls insertGuestToDB on main thread.
    insertGuestToDB(Guest("Joe","052352332",false,0))

    var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
    var rv: RecyclerView = rootView.findViewById(R.id.guests_list_recycler_view)
    rv.layoutManager = LinearLayoutManager(context)

    // [4] calls getAllDataFromDB on main thread.
    var d = getAllDataFromDB()
    // [6] get "d" on main thread. "d" is an empty list. 
    rv.adapter = GuestsRecycleViewAdapter.newInstance(d)

    return rootView
}


private fun insertGuestToDB(guest: Guest)
{
    // [2] insertGuestToDB executed on main thread.
    val task = Runnable {
        guestsDataBase?.guestsDao()?.insert(guest)
        // [7] insertion finished on worker thread.     
    }
    dbWorkerThread.postTask(task)
    // [3] insertGuestToDB finishes on main thread.
}

private fun getAllDataFromDB(): List<Guest>
{
    // [4] getAllDataFromDB on main thread. "data" points to an empty list
    var data: List<Guest> = emptyList()
    val task = Runnable {
        data = guestsDataBase?.guestsDao()?.getAllGuests()!!
        // [8] query finishes on worker thread, but "data" is now lost.
    }
    dbWorkerThread.postTask(task)

    // [5] returns on main thread. "data" still points to an empty list
    return data
}


您需要做的是等待 dbWorkerThread 完成其工作,然后再将 data 列表分配给 adapter。看看一个最简单的解决方案:


var rv: RecyclerView

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? 
{

    dbWorkerThread = DBWorkerThread("workerThread")
    dbWorkerThread.start()

    var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
    rv= rootView.findViewById(R.id.guests_list_recycler_view)
    rv.layoutManager = LinearLayoutManager(context)

    guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
    insertGuestToDbAndUpdateRv(Guest("Joe","052352332",false,0))

    return rootView
}


private fun insertGuestToDbAndUpdateRv(guest: Guest)
{
    val task = Runnable {
        // 1. Insert on a worker thread.
        guestsDataBase?.guestsDao()?.insert(guest)

        // 2. Query on a worker thread.
        var data = guestsDataBase?.guestsDao()?.getAllGuests()!!
        this@GuestListFragment.getActivity().runOnUiThread {
            // 3. Once they are done, update ui on main thread.
            rv.adapter = GuestsRecycleViewAdapter.newInstance(data)
        }
    }
    dbWorkerThread.postTask(task)
}


请注意,这绝对不是解决此问题的最佳方法。这是一个非常常见的问题,并且有许多不同的解决方案更具可读性、灵活性和可维护性。只是其他解决方案需要更深入的理解,而多线程是一个如此广泛的话题,我无法在一个 SO 答案中解释所有这些。不过,我希望您能大致了解并鼓励您探索其他解决方案。

【讨论】:

  • 这种情况下最好的方法是什么?
  • @Itay 我会说使用LiveData 或RxJava 的反应式设计可以说是最先进的解决方案。您可能已经熟悉它们。似乎使用其中之一的Android-room 教程比没有它们的教程更多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多