简体   繁体   中英

Room database view with RecyclerView

I'm new to Room and Kotlin, and I'm trying to build a simple Room's database and present it in a RecyclerView that sits in a ViewPager.

The Activity contains a ViewPager object, that ViewPager contains a Fragment, and that Fragment contains a RecyclerView: Activity --> ViewPager --> Fragment --> RecyclerView

The problems I have is that I get null when I'm trying to receive the database (after an insertion).

code:

@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:

@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:

@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
        }
    }
}

activity:

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
    }
}

adapter:

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

    override fun getCount(): Int
    {
        return 1
    }

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

fragment:

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
    }
}

You need to understand the concept of multi-threading to understand why your code does not work. In brief, you are getting an empty list because you are trying to get the list in main thread while your insertion and query running on the worker thread have not completed yet.

I put the comment in the code to explain the order of execution. The number in the comment is the order of completion:


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
}


What you need to do is to wait for dbWorkerThread to complete its job before assigning data list to the adapter . Take a look at one of the simplest solution:


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)
}


Be aware that this is definitely not the best way to tackle this problem. This is a very common problem and there are tons of different solutions that are much more readable, flexible and maintainable. It is just that other solutions require deeper understanding and multi-threading is such a broad topic that I cannot explain all of them in a single SO answer. I hope you get the general idea though and encourage you to explore other solutions to this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM