From 1dd8f3d24f845b041af1b1cdb633fee13d4b7e99 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 21:14:02 +0300 Subject: [PATCH 01/10] feature:added paging source --- .../example/android/codelabs/paging/data/GithubPagingSource.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt new file mode 100644 index 00000000..e69de29b From 4a286d3093b6ea1ca8557d39c4de99f80c36c367 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 21:27:10 +0300 Subject: [PATCH 02/10] build and configure pagingData in GithubRepository --- .idea/.gitignore | 3 ++ .idea/Paging-3.iml | 14 ++++++ .idea/misc.xml | 4 ++ .idea/modules.xml | 8 ++++ .idea/vcs.xml | 6 +++ .../paging/data/GithubPagingSource.kt | 44 +++++++++++++++++++ .../codelabs/paging/data/GithubRepository.kt | 36 +++++---------- .../android/codelabs/paging/Injection.kt | 4 +- basic/end/settings.gradle | 8 ++++ 9 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Paging-3.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Paging-3.iml b/.idea/Paging-3.iml new file mode 100644 index 00000000..697945a8 --- /dev/null +++ b/.idea/Paging-3.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..237afc07 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..8146eaef --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt index e69de29b..1f00db26 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubPagingSource.kt @@ -0,0 +1,44 @@ +// GitHub page API is 1 based: https://developer.github.com/v3/#pagination +private const val GITHUB_STARTING_PAGE_INDEX = 1 + +class GithubPagingSource( + private val service: GithubService, + private val query: String +) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult { + val position = params.key ?: GITHUB_STARTING_PAGE_INDEX + val apiQuery = query + IN_QUALIFIER + return try { + val response = service.searchRepos(apiQuery, position, params.loadSize) + val repos = response.items + val nextKey = if (repos.isEmpty()) { + null + } else { + // initial load size = 3 * NETWORK_PAGE_SIZE + // ensure we're not requesting duplicating items, at the 2nd request + position + (params.loadSize / NETWORK_PAGE_SIZE) + } + LoadResult.Page( + data = repos, + prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1, + nextKey = nextKey + ) + } catch (exception: IOException) { + return LoadResult.Error(exception) + } catch (exception: HttpException) { + return LoadResult.Error(exception) + } + } + // The refresh key is used for subsequent refresh calls to PagingSource.load after the initial load + override fun getRefreshKey(state: PagingState): Int? { + // We need to get the previous key (or next key if previous is null) of the page + // that was closest to the most recently accessed index. + // Anchor position is the most recently accessed index + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + +} \ No newline at end of file diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt index 855e8e9f..e9037b1d 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt @@ -26,38 +26,26 @@ import com.example.android.codelabs.paging.db.RepoDatabase import com.example.android.codelabs.paging.model.Repo import kotlinx.coroutines.flow.Flow -/** - * Repository class that works with local and remote data sources. +/** Paging 3 now : ) + * 1. Handles in-memory cache. + 2. Requests data when the user is close to the end of the list. */ -class GithubRepository( - private val service: GithubService, - private val database: RepoDatabase -) { - /** - * Search repositories whose names match the query, exposed as a stream of data that will emit - * every time we get more data from the network. - */ - fun getSearchResultStream(query: String): Flow> { - Log.d("GithubRepository", "New query: $query") - - // appending '%' so we can allow other characters to be before and after the query string - val dbQuery = "%${query.replace(' ', '%')}%" - val pagingSourceFactory = { database.reposDao().reposByName(dbQuery) } +class GithubRepository(private val service: GithubService) { - @OptIn(ExperimentalPagingApi::class) + fun getSearchResultStream(query: String): Flow> { return Pager( - config = PagingConfig(pageSize = NETWORK_PAGE_SIZE, enablePlaceholders = false), - remoteMediator = GithubRemoteMediator( - query, - service, - database + config = PagingConfig( + pageSize = NETWORK_PAGE_SIZE, + maxSize = NETWORK_MAX_SIZE, + enablePlaceholders = false ), - pagingSourceFactory = pagingSourceFactory + pagingSourceFactory = { GithubPagingSource(service, query) } ).flow } companion object { - const val NETWORK_PAGE_SIZE = 30 + const val NETWORK_PAGE_SIZE = 50 + const val NETWORK_MAX_SIZE = 150 } } diff --git a/advanced/start/app/src/main/java/com/example/android/codelabs/paging/Injection.kt b/advanced/start/app/src/main/java/com/example/android/codelabs/paging/Injection.kt index 2eba2eb3..f163e868 100644 --- a/advanced/start/app/src/main/java/com/example/android/codelabs/paging/Injection.kt +++ b/advanced/start/app/src/main/java/com/example/android/codelabs/paging/Injection.kt @@ -23,13 +23,13 @@ import com.example.android.codelabs.paging.data.GithubRepository import com.example.android.codelabs.paging.ui.ViewModelFactory /** - * Class that handles object creation. + * Class that handles creation. * Like this, objects can be passed as parameters in the constructors and then replaced for * testing, where needed. */ object Injection { - /** + /**object * Creates an instance of [GithubRepository] based on the [GithubService] and a * [GithubLocalCache] */ diff --git a/basic/end/settings.gradle b/basic/end/settings.gradle index cf84bb15..cd5b7bc6 100644 --- a/basic/end/settings.gradle +++ b/basic/end/settings.gradle @@ -3,6 +3,14 @@ // https://docs.gradle.org/current/userguide/platforms.html enableFeaturePreview("VERSION_CATALOGS") +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { From 3aad4fd76f76d5eca7ff37453e8797d7d78b91eb Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 21:42:53 +0300 Subject: [PATCH 03/10] Request and cache PagingData in the ViewModel --- .../paging/ui/SearchRepositoriesViewModel.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt index f4ccea8d..ec6707bf 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt @@ -45,7 +45,7 @@ import kotlinx.coroutines.launch */ class SearchRepositoriesViewModel( private val repository: GithubRepository, - private val savedStateHandle: SavedStateHandle + private val savedStateHandle: SavedStateHandle // SavedStateRegistryOwner ) : ViewModel() { /** @@ -53,7 +53,7 @@ class SearchRepositoriesViewModel( */ val state: StateFlow - val pagingDataFlow: Flow> + val pagingDataFlow: Flow> /** * Processor of side effects from the UI which in turn feedback into [state] @@ -61,6 +61,8 @@ class SearchRepositoriesViewModel( val accept: (UiAction) -> Unit init { + + // UiAction Stream val initialQuery: String = savedStateHandle.get(LAST_SEARCH_QUERY) ?: DEFAULT_QUERY val lastQueryScrolled: String = savedStateHandle.get(LAST_QUERY_SCROLLED) ?: DEFAULT_QUERY val actionStateFlow = MutableSharedFlow() @@ -80,6 +82,7 @@ class SearchRepositoriesViewModel( ) .onStart { emit(UiAction.Scroll(currentQuery = lastQueryScrolled)) } + // flows for both PagingData and UiState pagingDataFlow = searches .flatMapLatest { searchRepo(queryString = it.query) } .cachedIn(viewModelScope) @@ -113,7 +116,7 @@ class SearchRepositoriesViewModel( super.onCleared() } - private fun searchRepo(queryString: String): Flow> = + private fun searchRepo(queryString: String): Flow> = repository.getSearchResultStream(queryString) .map { pagingData -> pagingData.map { UiModel.RepoItem(it) } } .map { @@ -143,14 +146,16 @@ class SearchRepositoriesViewModel( } sealed class UiAction { - data class Search(val query: String) : UiAction() - data class Scroll(val currentQuery: String) : UiAction() + data class Search(val query: String) : UiAction() // query + data class Scroll(//val currentQuery: String, + val visibleItemCount: Int, + val lastVisibleItemPosition: Int, + val totalItemCount: Int) : UiAction() // scrolling down the screen to load alot of data } data class UiState( - val query: String = DEFAULT_QUERY, - val lastQueryScrolled: String = DEFAULT_QUERY, - val hasNotScrolledForCurrentSearch: Boolean = false + val query: String, + val searchResult: RepoSearchResult ) sealed class UiModel { From 28d68aadf869fa69f37720eef6233168dd39bb6c Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 21:51:11 +0300 Subject: [PATCH 04/10] Make the Adapter work with PagingData --- .../codelabs/paging/ui/ReposAdapter.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt index 0449ca49..4ab276d8 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt @@ -25,7 +25,7 @@ import com.example.android.codelabs.paging.R /** * Adapter for the list of repositories. */ -class ReposAdapter : PagingDataAdapter(UIMODEL_COMPARATOR) { +class ReposAdapter : PagingDataAdapter(REPO_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return if (viewType == R.layout.repo_view_item) { @@ -37,32 +37,32 @@ class ReposAdapter : PagingDataAdapter(UIMODEL_COMPARATOR) override fun getItemViewType(position: Int): Int { return when (getItem(position)) { - is UiModel.RepoItem -> R.layout.repo_view_item - is UiModel.SeparatorItem -> R.layout.separator_view_item + is Repo.RepoItem -> R.layout.repo_view_item + is Repo.SeparatorItem -> R.layout.separator_view_item null -> throw UnsupportedOperationException("Unknown view") } } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val uiModel = getItem(position) - uiModel.let { + val repo = getItem(position) + repo.let { when (uiModel) { - is UiModel.RepoItem -> (holder as RepoViewHolder).bind(uiModel.repo) - is UiModel.SeparatorItem -> (holder as SeparatorViewHolder).bind(uiModel.description) + is Repo.RepoItem -> (holder as RepoViewHolder).bind(repo.repo) + is Repo.SeparatorItem -> (holder as SeparatorViewHolder).bind(repo.description) } } } companion object { - private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: UiModel, newItem: UiModel): Boolean { - return (oldItem is UiModel.RepoItem && newItem is UiModel.RepoItem && + private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean { + return (oldItem is Repo.RepoItem && newItem is Repo.RepoItem && oldItem.repo.fullName == newItem.repo.fullName) || - (oldItem is UiModel.SeparatorItem && newItem is UiModel.SeparatorItem && + (oldItem is Repo.SeparatorItem && newItem is Repo.SeparatorItem && oldItem.description == newItem.description) } - override fun areContentsTheSame(oldItem: UiModel, newItem: UiModel): Boolean = + override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean = oldItem == newItem } } From cb90c5e9ff2f43b4c54ae193da3f6a566f0d796c Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 21:58:24 +0300 Subject: [PATCH 05/10] Ui update to handle pagingData from viewModel --- .../paging/ui/SearchRepositoriesActivity.kt | 60 +++---------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt index 23316fa7..936a72c6 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt @@ -48,12 +48,7 @@ class SearchRepositoriesActivity : AppCompatActivity() { setContentView(view) // get the view model - val viewModel = ViewModelProvider( - this, Injection.provideViewModelFactory( - context = this, - owner = this - ) - ) + val viewModel = ViewModelProvider(this, Injection.provideViewModelFactory(owner = this)) .get(SearchRepositoriesViewModel::class.java) // add dividers between RecyclerView's row items @@ -74,21 +69,17 @@ class SearchRepositoriesActivity : AppCompatActivity() { */ private fun ActivitySearchRepositoriesBinding.bindState( uiState: StateFlow, - pagingData: Flow>, + pagingData: Flow>, uiActions: (UiAction) -> Unit ) { val repoAdapter = ReposAdapter() - val header = ReposLoadStateAdapter { repoAdapter.retry() } - list.adapter = repoAdapter.withLoadStateHeaderAndFooter( - header = header, - footer = ReposLoadStateAdapter { repoAdapter.retry() } - ) + list.adapter = repoAdapter + bindSearch( uiState = uiState, onQueryChanged = uiActions ) bindList( - header = header, repoAdapter = repoAdapter, uiState = uiState, pagingData = pagingData, @@ -135,21 +126,21 @@ class SearchRepositoriesActivity : AppCompatActivity() { } private fun ActivitySearchRepositoriesBinding.bindList( - header: ReposLoadStateAdapter, repoAdapter: ReposAdapter, uiState: StateFlow, - pagingData: Flow>, + pagingData: Flow>, onScrollChanged: (UiAction.Scroll) -> Unit ) { - retryButton.setOnClickListener { repoAdapter.retry() } list.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy != 0) onScrollChanged(UiAction.Scroll(currentQuery = uiState.value.query)) } }) val notLoading = repoAdapter.loadStateFlow - .asRemotePresentationState() - .map { it == RemotePresentationState.PRESENTED } + // Only emit when REFRESH LoadState for the paging source changes. + .distinctUntilChangedBy { it.source.refresh } + // Only react to cases where REFRESH completes i.e., NotLoading. + .map { it.source.refresh is LoadState.NotLoading } val hasNotScrolledForCurrentSearch = uiState .map { it.hasNotScrolledForCurrentSearch } @@ -171,38 +162,5 @@ class SearchRepositoriesActivity : AppCompatActivity() { if (shouldScroll) list.scrollToPosition(0) } } - - lifecycleScope.launch { - repoAdapter.loadStateFlow.collect { loadState -> - // Show a retry header if there was an error refreshing, and items were previously - // cached OR default to the default prepend state - header.loadState = loadState.mediator - ?.refresh - ?.takeIf { it is LoadState.Error && repoAdapter.itemCount > 0 } - ?: loadState.prepend - - val isListEmpty = loadState.refresh is LoadState.NotLoading && repoAdapter.itemCount == 0 - // show empty list - emptyList.isVisible = isListEmpty - // Only show the list if refresh succeeds, either from the the local db or the remote. - list.isVisible = loadState.source.refresh is LoadState.NotLoading || loadState.mediator?.refresh is LoadState.NotLoading - // Show loading spinner during initial load or refresh. - progressBar.isVisible = loadState.mediator?.refresh is LoadState.Loading - // Show the retry state if initial load or refresh fails. - retryButton.isVisible = loadState.mediator?.refresh is LoadState.Error && repoAdapter.itemCount == 0 - // Toast on any error, regardless of whether it came from RemoteMediator or PagingSource - val errorState = loadState.source.append as? LoadState.Error - ?: loadState.source.prepend as? LoadState.Error - ?: loadState.append as? LoadState.Error - ?: loadState.prepend as? LoadState.Error - errorState?.let { - Toast.makeText( - this@SearchRepositoriesActivity, - "\uD83D\uDE28 Wooops ${it.error}", - Toast.LENGTH_LONG - ).show() - } - } - } } } From b36b7de432053fd8356f5e42b31a4bd37698bdf2 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 22:02:14 +0300 Subject: [PATCH 06/10] inifinite scrolling --- .../android/codelabs/paging/ui/SearchRepositoriesActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt index 936a72c6..cb44d63d 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt @@ -73,7 +73,10 @@ class SearchRepositoriesActivity : AppCompatActivity() { uiActions: (UiAction) -> Unit ) { val repoAdapter = ReposAdapter() - list.adapter = repoAdapter + list.adapter = repoAdapter.withLoadStateHeaderAndFooter( + header = ReposLoadStateAdapter { repoAdapter.retry() }, + footer = ReposLoadStateAdapter { repoAdapter.retry() } + ) bindSearch( uiState = uiState, From 386b6bd8359f417c7d962af5c6774a62428d5e76 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 22:09:16 +0300 Subject: [PATCH 07/10] Handling both Separator and paging data --- .../paging/ui/SearchRepositoriesActivity.kt | 15 +++++++++++++++ .../paging/ui/SearchRepositoriesViewModel.kt | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt index cb44d63d..8cbaf2f3 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt @@ -156,6 +156,19 @@ class SearchRepositoriesActivity : AppCompatActivity() { ) .distinctUntilChanged() + + // Collecting from loadStateFlow directly. + lifecycleScope.launch { + repoAdapter.loadStateFlow.collect { loadState -> + val isListEmpty = loadState.refresh is LoadState.NotLoading && repoAdapter.itemCount == 0 + // show empty list + emptyList.isVisible = isListEmpty + // Only show the list if refresh succeeds. + list.isVisible = !isListEmpty + } + } + + /* lifecycleScope.launch { pagingData.collectLatest(repoAdapter::submitData) } @@ -165,5 +178,7 @@ class SearchRepositoriesActivity : AppCompatActivity() { if (shouldScroll) list.scrollToPosition(0) } } + */ + } } diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt index ec6707bf..2f52e5da 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt @@ -116,7 +116,7 @@ class SearchRepositoriesViewModel( super.onCleared() } - private fun searchRepo(queryString: String): Flow> = + private fun searchRepo(queryString: String): Flow> = repository.getSearchResultStream(queryString) .map { pagingData -> pagingData.map { UiModel.RepoItem(it) } } .map { From ee68df4dc27464b0e9565fef5738ccf485107815 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 22:25:52 +0300 Subject: [PATCH 08/10] Adapter update to support UiModel --- .../codelabs/paging/ui/ReposAdapter.kt | 26 +++++++++---------- .../paging/ui/SearchRepositoriesViewModel.kt | 2 +- .../codelabs/paging/ui/SeparatorViewHolder.kt | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt index 4ab276d8..5e3a3f72 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/ReposAdapter.kt @@ -25,7 +25,7 @@ import com.example.android.codelabs.paging.R /** * Adapter for the list of repositories. */ -class ReposAdapter : PagingDataAdapter(REPO_COMPARATOR) { +class ReposAdapter : PagingDataAdapter(UIMODEL_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return if (viewType == R.layout.repo_view_item) { @@ -37,33 +37,33 @@ class ReposAdapter : PagingDataAdapter(REPO_COMPARATOR) { override fun getItemViewType(position: Int): Int { return when (getItem(position)) { - is Repo.RepoItem -> R.layout.repo_view_item - is Repo.SeparatorItem -> R.layout.separator_view_item + is UiModel.RepoItem -> R.layout.repo_view_item + is UiModel.SeparatorItem -> R.layout.separator_view_item null -> throw UnsupportedOperationException("Unknown view") } } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val repo = getItem(position) - repo.let { + val uiModel = getItem(position) + uiModel.let { when (uiModel) { - is Repo.RepoItem -> (holder as RepoViewHolder).bind(repo.repo) - is Repo.SeparatorItem -> (holder as SeparatorViewHolder).bind(repo.description) + is UiModel.RepoItem -> (holder as RepoViewHolder).bind(uiModel.repo) + is UiModel.SeparatorItem -> (holder as SeparatorViewHolder).bind(uiModel.description) } } } companion object { - private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean { - return (oldItem is Repo.RepoItem && newItem is Repo.RepoItem && + private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: UiModel, newItem: UiModel): Boolean { + return (oldItem is UiModel.RepoItem && newItem is UiModel.RepoItem && oldItem.repo.fullName == newItem.repo.fullName) || - (oldItem is Repo.SeparatorItem && newItem is Repo.SeparatorItem && + (oldItem is UiModel.SeparatorItem && newItem is UiModel.SeparatorItem && oldItem.description == newItem.description) } - override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean = - oldItem == newItem + override fun areContentsTheSame(oldItem: UiModel, newItem: UiModel): Boolean = + oldItem == newItem } } } \ No newline at end of file diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt index 2f52e5da..42319ea2 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesViewModel.kt @@ -116,7 +116,7 @@ class SearchRepositoriesViewModel( super.onCleared() } - private fun searchRepo(queryString: String): Flow> = + private fun searchRepo(queryString: String): Flow> = repository.getSearchResultStream(queryString) .map { pagingData -> pagingData.map { UiModel.RepoItem(it) } } .map { diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SeparatorViewHolder.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SeparatorViewHolder.kt index 812961d5..996097c1 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SeparatorViewHolder.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SeparatorViewHolder.kt @@ -33,7 +33,7 @@ class SeparatorViewHolder(view: View) : RecyclerView.ViewHolder(view) { companion object { fun create(parent: ViewGroup): SeparatorViewHolder { val view = LayoutInflater.from(parent.context) - .inflate(R.layout.separator_view_item, parent, false) + .inflate(R.layout.separator_view_item, parent, false) return SeparatorViewHolder(view) } } From 935ae856aa934c028a0495694b82d31d58c55142 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 22:38:51 +0300 Subject: [PATCH 09/10] Update the paging Flow creation on repository --- advanced/end/app/build.gradle | 1 + .../example/android/codelabs/paging/data/GithubRepository.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/advanced/end/app/build.gradle b/advanced/end/app/build.gradle index 3aaf3f2d..eaeb6627 100644 --- a/advanced/end/app/build.gradle +++ b/advanced/end/app/build.gradle @@ -51,6 +51,7 @@ android { } dependencies { + implementation freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"] implementation fileTree(dir: 'libs') implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines" diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt index e9037b1d..4112fcb2 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt @@ -31,7 +31,9 @@ import kotlinx.coroutines.flow.Flow 2. Requests data when the user is close to the end of the list. */ -class GithubRepository(private val service: GithubService) { +class GithubRepository(private val service: GithubService, + private val database: RepoDatabase +) { fun getSearchResultStream(query: String): Flow> { return Pager( From 61f9c290fa5ea9c104f9d3a8f18693daffe9c689 Mon Sep 17 00:00:00 2001 From: MuindiStephen Date: Sun, 22 Jan 2023 23:02:14 +0300 Subject: [PATCH 10/10] Feature/Finished Android Paging --- .../android/codelabs/paging/data/GithubRepository.kt | 8 +++++++- .../codelabs/paging/ui/SearchRepositoriesActivity.kt | 2 +- .../codelabs/paging/util/RemotePresentationState.kt | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 advanced/end/app/src/main/java/com/example/android/codelabs/paging/util/RemotePresentationState.kt diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt index 4112fcb2..382d6811 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/data/GithubRepository.kt @@ -35,14 +35,20 @@ class GithubRepository(private val service: GithubService, private val database: RepoDatabase ) { + // appending '%' so we can allow other characters to be before and after the query string + val dbQuery = "%${query.replace(' ', '%')}%" + val pagingSourceFactory = { database.reposDao().reposByName(dbQuery)} + fun getSearchResultStream(query: String): Flow> { + @OptIn(ExperimentalPagingApi::class) return Pager( config = PagingConfig( pageSize = NETWORK_PAGE_SIZE, maxSize = NETWORK_MAX_SIZE, enablePlaceholders = false ), - pagingSourceFactory = { GithubPagingSource(service, query) } + remoteMediator = GithubRemoteMediator(service, query, database) } + pagingSourceFactory = pagingSourceFactory ).flow } diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt index 8cbaf2f3..65fc8608 100644 --- a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/ui/SearchRepositoriesActivity.kt @@ -48,7 +48,7 @@ class SearchRepositoriesActivity : AppCompatActivity() { setContentView(view) // get the view model - val viewModel = ViewModelProvider(this, Injection.provideViewModelFactory(owner = this)) + val viewModel = ViewModelProvider(this, Injection.provideViewModelFactory(owner = this, context = this)) .get(SearchRepositoriesViewModel::class.java) // add dividers between RecyclerView's row items diff --git a/advanced/end/app/src/main/java/com/example/android/codelabs/paging/util/RemotePresentationState.kt b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/util/RemotePresentationState.kt new file mode 100644 index 00000000..df1b55bb --- /dev/null +++ b/advanced/end/app/src/main/java/com/example/android/codelabs/paging/util/RemotePresentationState.kt @@ -0,0 +1,3 @@ +enum class RemotePresentationState { + INITIAL, REMOTE_LOADING, SOURCE_LOADING, PRESENTED +} \ No newline at end of file