Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@

package com.example.android.codelabs.paging.data

import android.arch.lifecycle.MutableLiveData
import android.arch.paging.LivePagedListBuilder
import android.util.Log
import com.example.android.codelabs.paging.api.GithubService
import com.example.android.codelabs.paging.api.searchRepos
import com.example.android.codelabs.paging.db.GithubLocalCache
import com.example.android.codelabs.paging.model.RepoSearchResult

Expand All @@ -31,49 +30,31 @@ class GithubRepository(
private val cache: GithubLocalCache
) {

// keep the last requested page. When the request is successful, increment the page number.
private var lastRequestedPage = 1

// LiveData of network errors.
private val networkErrors = MutableLiveData<String>()

// avoid triggering multiple requests in the same time
private var isRequestInProgress = false

/**
* Search repositories whose names match the query.
*/
fun search(query: String): RepoSearchResult {
Log.d("GithubRepository", "New query: $query")
lastRequestedPage = 1
requestAndSaveData(query)

// Get data from the local cache
val data = cache.reposByName(query)

return RepoSearchResult(data, networkErrors)
}
// Get data source factory from the local cache
val dataSourceFactory = cache.reposByName(query)

fun requestMore(query: String) {
requestAndSaveData(query)
}
// every new query creates a new BoundaryCallback
// The BoundaryCallback will observe when the user reaches to the edges of
// the list and update the database with extra data
val boundaryCallback = RepoBoundaryCallback(query, service, cache)
val networkErrors = boundaryCallback.networkErrors

private fun requestAndSaveData(query: String) {
if (isRequestInProgress) return
// Get the paged list
val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.setBoundaryCallback(boundaryCallback)
.build()

isRequestInProgress = true
searchRepos(service, query, lastRequestedPage, NETWORK_PAGE_SIZE, { repos ->
cache.insert(repos, {
lastRequestedPage++
isRequestInProgress = false
})
}, { error ->
networkErrors.postValue(error)
isRequestInProgress = false
})
// Get the network errors exposed by the boundary callback
return RepoSearchResult(data, networkErrors)
}

companion object {
private const val NETWORK_PAGE_SIZE = 50
private const val DATABASE_PAGE_SIZE = 20
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.android.codelabs.paging.data

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.paging.PagedList
import android.util.Log
import com.example.android.codelabs.paging.api.GithubService
import com.example.android.codelabs.paging.api.searchRepos
import com.example.android.codelabs.paging.db.GithubLocalCache
import com.example.android.codelabs.paging.model.Repo

/**
* This boundary callback gets notified when user reaches to the edges of the list for example when
* the database cannot provide any more data.
**/
class RepoBoundaryCallback(
private val query: String,
private val service: GithubService,
private val cache: GithubLocalCache
) : PagedList.BoundaryCallback<Repo>() {

companion object {
private const val NETWORK_PAGE_SIZE = 50
}

// keep the last requested page. When the request is successful, increment the page number.
private var lastRequestedPage = 1

private val _networkErrors = MutableLiveData<String>()
// LiveData of network errors.
val networkErrors: LiveData<String>
get() = _networkErrors

// avoid triggering multiple requests in the same time
private var isRequestInProgress = false

/**
* Database returned 0 items. We should query the backend for more items.
*/
override fun onZeroItemsLoaded() {
Log.d("RepoBoundaryCallback", "onZeroItemsLoaded")
requestAndSaveData(query)
}

/**
* When all items in the database were loaded, we need to query the backend for more items.
*/
override fun onItemAtEndLoaded(itemAtEnd: Repo) {
Log.d("RepoBoundaryCallback", "onItemAtEndLoaded")
requestAndSaveData(query)
}

private fun requestAndSaveData(query: String) {
if (isRequestInProgress) return

isRequestInProgress = true
searchRepos(service, query, lastRequestedPage, NETWORK_PAGE_SIZE, { repos ->
cache.insert(repos) {
lastRequestedPage++
isRequestInProgress = false
}
}, { error ->
_networkErrors.postValue(error)
isRequestInProgress = false
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.example.android.codelabs.paging.db

import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.util.Log
import com.example.android.codelabs.paging.model.Repo
Expand All @@ -34,7 +33,7 @@ class GithubLocalCache(
/**
* Insert a list of repos in the database, on a background thread.
*/
fun insert(repos: List<Repo>, insertFinished: ()-> Unit) {
fun insert(repos: List<Repo>, insertFinished: () -> Unit) {
ioExecutor.execute {
Log.d("GithubLocalCache", "inserting ${repos.size} repos")
repoDao.insert(repos)
Expand All @@ -48,7 +47,7 @@ class GithubLocalCache(
* any characters between the words.
* @param name repository name
*/
fun reposByName(name: String): LiveData<List<Repo>> {
fun reposByName(name: String): DataSource.Factory<Int, Repo> {
// appending '%' so we can allow other characters to be before and after the query string
val query = "%${name.replace(' ', '%')}%"
return repoDao.reposByName(query)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.example.android.codelabs.paging.db

import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy
Expand All @@ -37,6 +37,6 @@ interface RepoDao {
// and order those results descending, by the number of stars and then by name
@Query("SELECT * FROM repos WHERE (name LIKE :queryString) OR (description LIKE " +
":queryString) ORDER BY stars DESC, name ASC")
fun reposByName(queryString: String): LiveData<List<Repo>>
fun reposByName(queryString: String): DataSource.Factory<Int, Repo>

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
package com.example.android.codelabs.paging.model

import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList

/**
* RepoSearchResult from a search, which contains LiveData<List<Repo>> holding query data,
* and a LiveData<String> of network error state.
*/
data class RepoSearchResult(
val data: LiveData<List<Repo>>,
val data: LiveData<PagedList<Repo>>,
val networkErrors: LiveData<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.example.android.codelabs.paging.ui

import android.support.v7.recyclerview.extensions.ListAdapter
import android.arch.paging.PagedListAdapter
import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
Expand All @@ -25,7 +25,7 @@ import com.example.android.codelabs.paging.model.Repo
/**
* Adapter for the list of repositories.
*/
class ReposAdapter : ListAdapter<Repo, RecyclerView.ViewHolder>(REPO_COMPARATOR) {
class ReposAdapter : PagedListAdapter<Repo, RecyclerView.ViewHolder>(REPO_COMPARATOR) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return RepoViewHolder.create(parent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.example.android.codelabs.paging.ui

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.arch.paging.PagedList
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DividerItemDecoration
Expand All @@ -31,6 +32,7 @@ import android.widget.Toast
import com.example.android.codelabs.paging.R
import com.example.android.codelabs.paging.Injection
import com.example.android.codelabs.paging.model.Repo
import kotlinx.android.synthetic.clearFindViewByIdCache
import kotlinx.android.synthetic.main.activity_search_repositories.*


Expand All @@ -50,22 +52,26 @@ class SearchRepositoriesActivity : AppCompatActivity() {
// add dividers between RecyclerView's row items
val decoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
list.addItemDecoration(decoration)
setupScrollListener()

initAdapter()
val query = savedInstanceState?.getString(LAST_SEARCH_QUERY) ?: DEFAULT_QUERY
viewModel.searchRepo(query)
initSearch(query)
}

override fun onDestroy() {
super.onDestroy()
clearFindViewByIdCache()
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(LAST_SEARCH_QUERY, viewModel.lastQueryValue())
}

private fun initAdapter() {
list.adapter = adapter
viewModel.repos.observe(this, Observer<List<Repo>> {
viewModel.repos.observe(this, Observer<PagedList<Repo>> {
Log.d("Activity", "list: ${it?.size}")
showEmptyList(it?.size == 0)
adapter.submitList(it)
Expand All @@ -78,22 +84,22 @@ class SearchRepositoriesActivity : AppCompatActivity() {
private fun initSearch(query: String) {
search_repo.setText(query)

search_repo.setOnEditorActionListener({ _, actionId, _ ->
search_repo.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
updateRepoListFromInput()
true
} else {
false
}
})
search_repo.setOnKeyListener({ _, keyCode, event ->
}
search_repo.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
updateRepoListFromInput()
true
} else {
false
}
})
}
}

private fun updateRepoListFromInput() {
Expand All @@ -116,20 +122,6 @@ class SearchRepositoriesActivity : AppCompatActivity() {
}
}

private fun setupScrollListener() {
val layoutManager = list.layoutManager as LinearLayoutManager
list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val totalItemCount = layoutManager.itemCount
val visibleItemCount = layoutManager.childCount
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()

viewModel.listScrolled(visibleItemCount, lastVisibleItem, totalItemCount)
}
})
}

companion object {
private const val LAST_SEARCH_QUERY: String = "last_search_query"
private const val DEFAULT_QUERY = "Android"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations
import android.arch.lifecycle.ViewModel
import android.arch.paging.PagedList
import com.example.android.codelabs.paging.data.GithubRepository
import com.example.android.codelabs.paging.model.Repo
import com.example.android.codelabs.paging.model.RepoSearchResult
Expand All @@ -30,16 +31,12 @@ import com.example.android.codelabs.paging.model.RepoSearchResult
*/
class SearchRepositoriesViewModel(private val repository: GithubRepository) : ViewModel() {

companion object {
private const val VISIBLE_THRESHOLD = 5
}

private val queryLiveData = MutableLiveData<String>()
private val repoResult: LiveData<RepoSearchResult> = Transformations.map(queryLiveData, {
repository.search(it)
})

val repos: LiveData<List<Repo>> = Transformations.switchMap(repoResult,
val repos: LiveData<PagedList<Repo>> = Transformations.switchMap(repoResult,
{ it -> it.data })
val networkErrors: LiveData<String> = Transformations.switchMap(repoResult,
{ it -> it.networkErrors })
Expand All @@ -51,15 +48,6 @@ class SearchRepositoriesViewModel(private val repository: GithubRepository) : Vi
queryLiveData.postValue(queryString)
}

fun listScrolled(visibleItemCount: Int, lastVisibleItemPosition: Int, totalItemCount: Int) {
if (visibleItemCount + lastVisibleItemPosition + VISIBLE_THRESHOLD >= totalItemCount) {
val immutableQuery = lastQueryValue()
if (immutableQuery != null) {
repository.requestMore(immutableQuery)
}
}
}

/**
* Get the last query value.
*/
Expand Down