Get item by id in Room

P. Savrov picture P. Savrov · Oct 25, 2017 · Viewed 18.3k times · Source

I'm using Room + LiveData in my Android project. Following to Google Blueprints, I've implemented data layer of my application.

This is how my Dao looks like:

@Query("SELECT * FROM events WHERE id=:arg0")
    fun loadSingle(id: String): LiveData<Event>

I'm calling it from my EventRepository:

fun loadSingle(eventId: String): LiveData<RequestReader<Event>> {
        return object: NetworkManager<Event, Event>(appExecutors!!) {

            override fun loadLocal(): LiveData<Event> {
                val item = eventLocal!!.loadSingle("Title 1")
                Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::loadLocal=$item")
                return item
            }

            override fun isUpdateForced(data: Event?): Boolean {
                Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::isUpdateForced")
                return data == null || requestTimeout.isAllowed(UNDEFINED_KEY.toString())
            }

            override fun makeRequest(): LiveData<ApiResponse<Event>> {
                Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::makeRequest")
                return Database.createService(EventRemote::class.java).load(eventId)
            }

            override fun onSuccess(item: Event) {
                eventLocal?.save(item)
            }

            override fun onFail() {
                Crashlytics.log(Log.VERBOSE, TAG, "loadFromServer::onFail")
                requestTimeout.reset(UNDEFINED_KEY.toString())
            }

        }.liveData
    }

Where NetworkManager class is (has been "taken" from here):

    abstract class NetworkManager<ResultType, RequestType> @MainThread constructor(val appExecutors: AppExecutors) {

        companion object {
            private val TAG = "TAG_NETWORK_MANAGER"
        }

        val liveData: MediatorLiveData<RequestReader<ResultType>> = MediatorLiveData()

        init {
            liveData.value = RequestReader.loading(null)
            val localSource: LiveData<ResultType> = loadLocal()
            Log.d(TAG, "before add::localSource=${localSource.value}")
            liveData.addSource(localSource, { data ->
                Log.d(TAG, "data=$data")
                liveData.removeSource(localSource)
                if (isUpdateForced(data)) {
                    loadRemote(localSource)
                } else {
                    liveData.addSource(localSource, { reusedData -> liveData.value = RequestReader.success(reusedData)})
                }
            })
        }

        private fun loadRemote(localSource: LiveData<ResultType>) {
            val remoteSource = makeRequest()
            liveData.addSource(localSource, {
                liveData.value = RequestReader.success(it)
            })
            liveData.addSource(remoteSource) { response ->
                liveData.removeSource(localSource)
                liveData.removeSource(remoteSource)
                if (response!!.isSuccessful) {
                    appExecutors.diskIO.execute {
                        onSuccess(processResponse(response))
                        appExecutors.mainThread.execute {
                            liveData.addSource(localSource, {
                                liveData.value = RequestReader.success(it)
                            })
                        }
                    }
                } else {
                    onFail()
                    liveData.addSource(localSource, {
                        liveData.value = RequestReader.error("Error: ${response.errorMessage}", it)
                    })
                }
            }

        }

        @MainThread
        protected abstract fun loadLocal(): LiveData<ResultType>

        @MainThread
        protected abstract fun isUpdateForced(data: ResultType?): Boolean

        @MainThread
        protected abstract fun makeRequest(): LiveData<ApiResponse<RequestType>>

        @WorkerThread
        protected abstract fun onSuccess(item: RequestType)

        @MainThread
        protected abstract fun onFail()

        @WorkerThread
        protected fun processResponse(response: ApiResponse<RequestType>): RequestType {
        return response.body!!
    }
}

And after i expect to get my LiveData in ViewModel:

open class EventSingleViewModel: ViewModel(), RepositoryComponent.Injectable {

    companion object {
        private val TAG = "TAG_EVENT_SINGLE_VIEW_MODEL"
    }

    @Inject lateinit var eventRepository: EventRepository

    var eventSingle: LiveData<RequestReader<Event>>? = null

    override fun inject(repositoryComponent: RepositoryComponent) {
        repositoryComponent.inject(this)
        eventSingle = MutableLiveData<RequestReader<Event>>()
    }

    fun load(eventId: String) {
        Crashlytics.log(Log.VERBOSE, TAG, "starts to loadList::eventId=$eventId")
        eventSingle = eventRepository.loadSingle(eventId)
    }

}

The problem. I'm getting a list of events the same way (it works!) I've described above, but with a single event (this event is already in database) it doesn't work. I've found out that localSource.value is null (in NetworkManager). Maybe my query is bad or.. something else.

Answer

Fredy Mederos picture Fredy Mederos · Oct 25, 2017

Check again your DAO implementation, the argument must be the same in both, the function parameter and the annotation arg.

Change this:

@Query("SELECT * FROM events WHERE id=:arg0")
fun loadSingle(id: String): LiveData<Event>

To:

@Query("SELECT * FROM events WHERE id=:id ")
fun loadSingle(id: String): LiveData<Event>