Android Kotlin Room Repository无法从详细信息活动中检索行 [英] Android Kotlin Room Repository unable to retrieve row from within detail activity

查看:72
本文介绍了Android Kotlin Room Repository无法从详细信息活动中检索行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我真的在为此&希望能有所帮助.我正在学习 Android Kotlin &构建一个在 RecyclerView 中显示步行路线(从云下载)列表的应用程序,并且选择了一条路线后,我想显示该路线的所有详细信息-一个简单的Master-Detail应用程序.既然如此,我正在学习,我也想尝试并使用最佳实践.使用Room数据库&进行的大多数工作都可以正常进行.存储库.数据库已正确填充,并且RecyclerView显示路线列表.选择路由后,routeID和其他详细信息将正确传递给活动(TwalksRouteActivity.kt),以显示详细信息&效果很好.

I'm really struggling with this & would appreciate some help please. I'm learning Android Kotlin & building an app that displays a list of walking routes (downloaded from the cloud) in a RecyclerView &, when a route is selected I want to display all details of the route - a simple Master-Detail app. Since, I'm learning I also want to try and use best practice. I have most of it working fine using a Room database & a Repository. The database is correctly populated and the RecyclerView displays the list of routes. When a route is selected the routeID and other details are correctly passed to an activity (TwalksRouteActivity.kt) to display the details & this works fine.

但是,我需要使用routeID从数据库(存储库?)中查找路由,因此所有详细信息都可以在detail活动中使用,但我无法使用它.我不想传递捆绑中的所有详细信息,因为一旦此操作有效,我将需要从详细信息活动中进行其他数据库查找.我尝试过围绕协程的各种解决方案,以避免线程阻塞,但是完全失败了.所以我的问题是,如何从明细活动中正确地从数据库/存储库中获取行.

However, I need to use the routeID to looks up the route from the database (Repository?) so all the details are available in the detail activity but I can't get this to work. I don't want to pass all of the details in a bundle because I will need to do other database look ups from the detail activity once this is working. I have tried all sorts of solutions around Coroutines to avoid thread blocking but have failed completely. So my question is, how do I correctly get a row from my database/repository from the detail activity.

这是详细活动(TwalksRouteActivity.kt):

Here's the detail activity (TwalksRouteActivity.kt):

package com.example.android.twalks.ui

import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.android.twalks.R
import com.example.android.twalks.database.RouteDao
import com.example.android.twalks.database.getDatabase
import com.example.android.twalks.domain.Route
import com.example.android.twalks.repository.RoutesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import timber.log.Timber.*

class TwalksRouteActivity() : AppCompatActivity()  {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var bundle: Bundle? = intent.extras
        var routeID = bundle?.getInt("routeID")
        var routeName = bundle?.getString("routeName")
        var routeCategoryName = bundle?.getString("routeCategoryName")
        var routeDistance = bundle?.getString("routeDistance")
        var routeTime = bundle?.getString("routeTime")
        var routeImageFile = bundle?.getString("routeImageFile")

        GlobalScope.launch (Dispatchers.Main) {
            val database = getDatabase(application)
            val routesRepository = RoutesRepository(database)
            val selectedRoute = routesRepository.getRoute(routeID)
            Log.d("CWM", selectedRoute.toString())

        }

        setContentView(R.layout.route_detail)

        val routeName_Text: TextView = findViewById(R.id.routeName_text)
        routeName_Text.text = routeName.toString()
        val routeID_Text: TextView = findViewById(R.id.routeID)
        routeID_Text.text = routeID.toString()

        //Toast.makeText(this,"Here in TwalksRouteActivity", Toast.LENGTH_LONG).show()
        //Toast.makeText(applicationContext,routeName,Toast.LENGTH_LONG)
    }
}

DatabaseEntities.kt

DatabaseEntities.kt

package com.example.android.twalks.database

import androidx.room.Entity
import androidx.room.PrimaryKey
import com.example.android.twalks.domain.Route

/**
 * DataTransferObjects go in this file. These are responsible for parsing responses from the server
 * or formatting objects to send to the server. You should convert these to domain objects before
 * using them.
 */

@Entity
data class DatabaseRoute constructor(
        @PrimaryKey
        val routeID: String,
        val routeName: String,
        val routeImageFile: String,
        val routeCategoryName: String,
        val routeCategory: String,
        val routeDistance: String,
        val routeTime: String,
        val routeStatus:String)

fun List<DatabaseRoute>.asDomainModel(): List<Route> {
        return map {
                Route(
                        routeID = it.routeID,
                        routeName = it.routeName,
                        routeImageFile = it.routeImageFile,
                        routeCategoryName = it.routeCategoryName,
                        routeCategory = it.routeCategory,
                        routeDistance = it.routeDistance,
                        routeTime = it.routeTime,
                        routeStatus = it.routeStatus)
        }
}

请注意,GlobalScope块在日志中返回kotlin.Unit,因此不会返回任何记录.这是我需要帮助的地方!

Note that the GlobalScope block returns a kotlin.Unit in the log so no record is being returned. This is where I need help!

Room.kt

package com.example.android.twalks.database

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.room.*
import com.example.android.twalks.domain.Route

@Dao
interface RouteDao {
    @Query("select * from databaseroute")
    fun getRoutes(): LiveData<List<DatabaseRoute>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(vararg routes: DatabaseRoute)

    @Query("select * from databaseroute where routeID = :routeID")
    fun getRoute(routeID: Int?): LiveData<Route>
}

@Database(entities = [DatabaseRoute::class],version = 1)
abstract class RoutesDatabase: RoomDatabase() {
    abstract val routeDao: RouteDao
}

private lateinit var INSTANCE: RoutesDatabase

fun getDatabase(context: Context): RoutesDatabase {
    synchronized(RoutesDatabase::class.java) {
        if (!::INSTANCE.isInitialized) {
            INSTANCE = Room.databaseBuilder(context.applicationContext,
                    RoutesDatabase::class.java,
                    "routes").build()
        }
    }
    return INSTANCE
}

Models.kt(域对象):

Models.kt (Domain Object):

package com.example.android.twalks.domain

/**
 * Domain objects are plain Kotlin data classes that represent the things in our app. These are the
 * objects that should be displayed on screen, or manipulated by the app.
 *
 * @see database for objects that are mapped to the database
 * @see network for objects that parse or prepare network calls
 */

data class Route(val routeID: String,
                 val routeName: String,
                 val routeImageFile: String,
                 val routeCategoryName: String,
                 val routeCategory: String,
                 val routeDistance: String,
                 val routeTime: String,
                 val routeStatus: String)

DataTransferObjects.kt:

DataTransferObjects.kt:

package com.example.android.twalks.network

import android.os.Parcelable
import com.example.android.twalks.database.DatabaseRoute
import com.example.android.twalks.domain.Route
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize

@JsonClass(generateAdapter = true)
data class NetworkRouteContainer(val routes: List<NetworkRoute>)


@JsonClass(generateAdapter = true)
data class NetworkRoute(
        val routeID: String,
        val routeName: String,
        val routeImageFile: String,
        val routeCategoryName: String,
        val routeCategory: String,
        val routeDistance: String,
        val routeTime: String,
        val routeStatus: String )
/**
 * Convert Network results to com.example.android.twalks.database objects
 */

fun NetworkRouteContainer.asDomainModel(): List<Route> {
    return routes.map {
        Route(
                routeID = it.routeID,
                routeName = it.routeName,
                routeImageFile = it.routeImageFile,
                routeCategoryName = it.routeCategoryName,
                routeCategory = it.routeCategory,
                routeDistance = it.routeDistance,
                routeTime = it.routeTime,
                routeStatus = it.routeStatus)
    }
}

fun NetworkRouteContainer.asDatabaseModel(): Array<DatabaseRoute> {
    return routes.map {
        DatabaseRoute(
                routeID = it.routeID,
                routeName = it.routeName,
                routeImageFile = it.routeImageFile,
                routeCategoryName = it.routeCategoryName,
                routeCategory = it.routeCategory,
                routeDistance = it.routeDistance,
                routeTime = it.routeTime,
                routeStatus = it.routeStatus
        )
    }.toTypedArray()
}

RoutesRepository:

RoutesRepository:

package com.example.android.twalks.repository

import android.util.Log
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.example.android.twalks.database.RouteDao
import com.example.android.twalks.database.RoutesDatabase
import com.example.android.twalks.database.asDomainModel

import com.example.android.twalks.domain.Route
import com.example.android.twalks.network.Network
import com.example.android.twalks.network.asDatabaseModel


import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber

/**
 * Repository for fetching routes from the network and storing them on disk
 */
class RoutesRepository(private val database: RoutesDatabase) {

    val routes: LiveData<List<Route>> =
            Transformations.map(database.routeDao.getRoutes()) {
                it.asDomainModel()
            }

    suspend fun refreshRoutes() {
        withContext(Dispatchers.IO) {
            val routelist = Network.twalks.getRoutes().await()
            database.routeDao.insertAll(*routelist.asDatabaseModel())
                    }
    }

    suspend fun getRoute(id: Int?) {
        withContext(Dispatchers.IO) {
            val route: LiveData<Route> = database.routeDao.getRoute(id)
            //Log.d("CWM2",route.toString())
            return@withContext route
            }
    }
}

推荐答案

您的代码无法正常工作,因为您没有从RoutesRepository类中的getRoute返回任何内容.指定返回类型,您将看到它.

Your code is not working because you're not returning anything from getRoute in your RoutesRepository class. Specify the return type and you'll see it.

您可以通过返回withContext块来解决它,但是我想向您建议一些更改,因为您说自己正在学习并且还想尝试并应用最佳实践.

You can solve it by returning the withContext block, but I'd like to suggest you some changes since you said you're learning and also want to try and apply best practices.

自2.1版开始,Room支持协程.您要做的就是用关键字suspend标记您的DAO方法.您不必担心在主线程上调用suspend DAO方法,因为它被挂起并且Room可以在后台线程上执行查询.

Room supports coroutines since version 2.1. All you have to do is marking your DAO methods with the keyword suspend. You don't have to worry about calling a suspend DAO method on your Main thread since it gets suspended and Room manages to execute the query on a background thread.

  • Learn more about this subject here.

因此,您的getRoute DAO方法将如下所示:

So your getRoute DAO method would look like this:

@Query("select * from databaseroute where routeID = :routeID")
suspend fun getRoute(routeID: Int): Route

注意1:我将返回类型从LiveData<Route>更改为Route,因为我认为您不希望它会更改.

Note 1: I changed the return type from LiveData<Route> to Route since I assume you don't expect it to change.

注释2:我看不到使用可为空的routeID作为参数的意义,因此我删除了?.

Note 2: I don't see the point in having a nullable routeID as argument so I removed the ?.

通过上一次更改,您在RoutesRepository类上的getRoute方法将如下所示:

With the previous change your getRoute method on your RoutesRepository class would look like this:

suspend fun getRoute(id: Int) = database.routeDao.getRoute(id)

注1:如前所述,您不必担心转移到后台线程,因为Room会为您完成.

Note 1: As I mentioned before, you don't have to worry about moving to a background thread since Room will do it for you.

注释2:再次说明,参数不能为空.

Note 2: Again, not nullable argument.

您是直接从活动中调用存储库.我不确定您要应用的体系结构,但希望在中间看到一个Presenter或ViewModel.忽略该细节,我建议您避免总是使用GlobalScope来启动协程.仅当您知道GlobalScope的工作原理并且完全确定自己在做什么时,才使用GlobalScope.

You're calling your repository directly from your activity. I'm not sure about the architecture you're applying but I would expect to see a Presenter or a ViewModel in the middle. Omitting that detail, I suggest you to avoid starting a coroutine with GlobalScope almost always. Use GlobalScope only when you know how GlobalScope works and you're totally sure about what you're doing.

  • 此处了解更多信息.. li>
  • Learn more about this subject here.

您可以使用lifecycleScope代替GlobalScope,它在主线程上运行并且具有生命周期意识.

Instead of GlobalScope you can use lifecycleScope which runs on the main thread and it's lifecycle aware.

将您的GlobalScope.launch {...}更改为此:

lifecycleScope.launch {
    ...
    val selectedRoute = routesRepository.getRoute(routeID)
    //Do something with selectedRoute here
}

注释1:.您需要androidx.lifecycle:lifecycle-runtime-ktx:2.2.0或更高版本.

Note 1: You need androidx.lifecycle:lifecycle-runtime-ktx:2.2.0 or higher.

注意2::如果您在请求中获取了所有路线数据,则只能将其routeID传递给新活动.

Note 2: If you're getting all the Route data in your request, you could pass only its routeID to your new activity.

这篇关于Android Kotlin Room Repository无法从详细信息活动中检索行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆