@@ -28,6 +28,7 @@ import com.example.android.codelabs.paging.db.RepoDatabase
2828import com.example.android.codelabs.paging.model.Repo
2929import retrofit2.HttpException
3030import java.io.IOException
31+ import java.io.InvalidObjectException
3132
3233// GitHub page API is 1 based: https://developer.github.com/v3/#pagination
3334const val GITHUB_STARTING_PAGE_INDEX = 1
@@ -38,50 +39,74 @@ class GithubRemoteMediator(
3839 private val repoDatabase : RepoDatabase
3940) : RemoteMediator<Int, Repo>() {
4041
42+ /* *
43+ * The paging library will call this method when there is no more data in the database to
44+ * load from.
45+ *
46+ * For every item we retrieve we store the previous and next keys, so we know how to request
47+ * more data, based on that specific item.
48+ *
49+ * state.pages holds the pages that were loaded until now and saved in the database.
50+ *
51+ * If the load type is REFRESH it means that we are in one of the following situations:
52+ * * it's the first time when the query was triggered and there was nothing saved yet in the
53+ * database
54+ * * a swipe to refresh was called
55+ * If the load type is START, we need to rely on the first item available in the pages
56+ * retrieved so far to compute they key for the previous page.
57+ * If the load type is END, we need to rely on the last item available in the pages
58+ * retrieved so far to compute they key for the next page.
59+ */
4160 override suspend fun load (loadType : LoadType , state : PagingState <Int , Repo >): MediatorResult {
42- Log .d(" GithubRemoteMediator" , " load type: $loadType " )
43-
44- if (loadType == LoadType .REFRESH ) {
45- repoDatabase.withTransaction {
46- repoDatabase.reposDao().clearRepos()
47- repoDatabase.remoteKeysDao().clearRemoteKeys()
48- }
49- }
50- val remoteKeys = repoDatabase.remoteKeysDao().remoteKeys()
51- ? : RemoteKeys (null , GITHUB_STARTING_PAGE_INDEX )
52-
5361 val page = when (loadType) {
54- LoadType .REFRESH -> remoteKeys.nextKey
55- LoadType .START -> remoteKeys.prevKey
56- LoadType .END -> remoteKeys.nextKey
57- }
58-
59- if (page == null ) {
60- return MediatorResult .Success (canRequestMoreData = false )
62+ LoadType .REFRESH -> {
63+ val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
64+ remoteKeys?.nextKey?.minus(1 ) ? : GITHUB_STARTING_PAGE_INDEX
65+ }
66+ LoadType .START -> {
67+ val remoteKeys = getRemoteKeyForFirstItem(state)
68+ if (remoteKeys == null ) {
69+ // The LoadType is START so some data was loaded before,
70+ // so we should have been able to get remote keys
71+ // If the remoteKeys are null, then we're an invalid state and we have a bug
72+ throw InvalidObjectException (" Remote key and the prevKey should not be null" )
73+ }
74+ // If the previous key is null, then we can't request more data
75+ val prevKey = remoteKeys.prevKey
76+ if (prevKey == null ) {
77+ return MediatorResult .Success (canRequestMoreData = false )
78+ }
79+ remoteKeys.prevKey
80+ }
81+ LoadType .END -> {
82+ val remoteKeys = getRemoteKeyForLastItem(state)
83+ if (remoteKeys?.nextKey == null ) {
84+ // The LoadType is END, so some data was loaded before,
85+ // so we should have been able to get remote keys
86+ // If the remoteKeys are null, then we're an invalid state and we have a bug
87+ throw InvalidObjectException (" Remote key should not be null for $loadType " )
88+ }
89+ remoteKeys.nextKey
90+ }
6191 }
6292
63- Log .d(" GithubRemoteMediator" , " page: $page " )
64-
6593 val apiQuery = query + IN_QUALIFIER
94+
6695 try {
6796 val apiResponse = service.searchRepos(apiQuery, page, state.config.pageSize)
6897
6998 return if (apiResponse.isSuccessful) {
7099 val repos = apiResponse.body()?.items ? : emptyList()
71100 val canRequestMoreData = repos.isNotEmpty()
72- val newRemoteKeys = RemoteKeys (
73- prevKey = remoteKeys.nextKey,
74- nextKey = if (canRequestMoreData) {
75- (remoteKeys.nextKey ? : GITHUB_STARTING_PAGE_INDEX ) + 1
76- } else {
77- null
78- }
79- )
80101 repoDatabase.withTransaction {
81- repoDatabase.remoteKeysDao().replace(newRemoteKeys)
102+ val prevKey = if (page == GITHUB_STARTING_PAGE_INDEX ) null else page - 1
103+ val nextKey = if (canRequestMoreData) page + 1 else null
104+ val keys = repos.map {
105+ RemoteKeys (repoId = it.id, prevKey = prevKey, nextKey = nextKey)
106+ }
107+ repoDatabase.remoteKeysDao().insertAll(keys)
82108 repoDatabase.reposDao().insert(repos)
83109 }
84- Log .d(" GithubRemoteMediator" , " data: ${repos.size} " )
85110 MediatorResult .Success (canRequestMoreData = canRequestMoreData)
86111 } else {
87112 MediatorResult .Error (IOException (apiResponse.message()))
@@ -92,4 +117,46 @@ class GithubRemoteMediator(
92117 return MediatorResult .Error (exception)
93118 }
94119 }
120+
121+ /* *
122+ * Get the remote keys of on the last item retrieved
123+ */
124+ private suspend fun getRemoteKeyForLastItem (state : PagingState <Int , Repo >): RemoteKeys ? {
125+ // Get the last page that was retrieved, that contained items.
126+ // From that last page, get the last item
127+ return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
128+ ?.let { repo ->
129+ // Get the remote keys of the last item retrieved
130+ repoDatabase.remoteKeysDao().remoteKeysRepoId(repo.id)
131+ }
132+ }
133+
134+ /* *
135+ * Get the remote keys of the first item retrieved
136+ */
137+ private suspend fun getRemoteKeyForFirstItem (state : PagingState <Int , Repo >): RemoteKeys ? {
138+ // Get the first page that was retrieved, that contained items.
139+ // From that first page, get the first item
140+ return state.pages.firstOrNull() { it.data.isNotEmpty() }?.data?.firstOrNull()
141+ ?.let { repo ->
142+ // Get the remote keys of the first items retrieved
143+ repoDatabase.remoteKeysDao().remoteKeysRepoId(repo.id)
144+ }
145+ }
146+
147+ /* *
148+ * Get the remote keys of the closest item to the anchor position
149+ */
150+ private suspend fun getRemoteKeyClosestToCurrentPosition (
151+ state : PagingState <Int , Repo >
152+ ): RemoteKeys ? {
153+ // The paging library is trying to load data after the anchor position
154+ // Get the item closest to the anchor position
155+ return state.anchorPosition?.let { position ->
156+ state.closestItemToPosition(position)?.id?.let { repoId ->
157+ // Get the remote keys of the item
158+ repoDatabase.remoteKeysDao().remoteKeysRepoId(repoId)
159+ }
160+ }
161+ }
95162}
0 commit comments