diff --git a/app/src/main/java/org/mozilla/fenix/components/history/PagedHistoryProvider.kt b/app/src/main/java/org/mozilla/fenix/components/history/PagedHistoryProvider.kt index 5da8c342de..0d72a5f187 100644 --- a/app/src/main/java/org/mozilla/fenix/components/history/PagedHistoryProvider.kt +++ b/app/src/main/java/org/mozilla/fenix/components/history/PagedHistoryProvider.kt @@ -76,9 +76,9 @@ interface PagedHistoryProvider { * * @param offset How much to offset the list by * @param numberOfItems How many items to fetch - * @param onComplete A callback that returns the list of [HistoryDB] + * @return list of [HistoryDB] */ - fun getHistory(offset: Int, numberOfItems: Int, onComplete: (List) -> Unit) + fun getHistory(offset: Int, numberOfItems: Int): List } /** @@ -112,15 +112,11 @@ class DefaultPagedHistoryProvider( @Volatile private var historyGroups: List? = null - @Suppress("LongMethod") override fun getHistory( offset: Int, - numberOfItems: Int, - onComplete: (List) -> Unit, - ) { - // A PagedList DataSource runs on a background thread automatically. - // If we run this in our own coroutineScope it breaks the PagedList - runBlockingIncrement { + numberOfItems: Int + ): List { + return runBlockingIncrement { // We need to re-fetch all the history metadata if the offset resets back at 0 // in the case of a pull to refresh. if (historyGroups == null || offset == 0) { @@ -146,7 +142,7 @@ class DefaultPagedHistoryProvider( .toList() } - onComplete(getHistoryAndSearchGroups(offset, numberOfItems)) + getHistoryAndSearchGroups(offset, numberOfItems) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt index 277886a0d3..0ed39765f9 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt @@ -6,14 +6,14 @@ package org.mozilla.fenix.library.history import android.view.LayoutInflater import android.view.ViewGroup -import androidx.paging.PagedListAdapter +import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.library.history.viewholders.HistoryListItemViewHolder class HistoryAdapter( private val historyInteractor: HistoryInteractor, -) : PagedListAdapter(historyDiffCallback), +) : PagingDataAdapter(historyDiffCallback), SelectionHolder { private var mode: HistoryFragmentState.Mode = HistoryFragmentState.Mode.Normal diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSource.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSource.kt index 87a732f4fe..cfb59640ba 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSource.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSource.kt @@ -5,38 +5,40 @@ package org.mozilla.fenix.library.history import androidx.annotation.VisibleForTesting -import androidx.paging.ItemKeyedDataSource +import androidx.paging.PagingSource +import androidx.paging.PagingState import org.mozilla.fenix.components.history.HistoryDB import org.mozilla.fenix.components.history.PagedHistoryProvider class HistoryDataSource( - private val historyProvider: PagedHistoryProvider -) : ItemKeyedDataSource() { + private val historyProvider: PagedHistoryProvider, + private val onZeroItemsLoaded: () -> Unit +) : PagingSource() { - // Because the pagination is not based off of the key - // we want to start at 1, not 0 to be able to send the correct offset - // to the `historyProvider.getHistory` call. - override fun getKey(item: History): Int = item.position + 1 + // having any value but null creates visual glitches in case or swipe to refresh and immediate + // scroll down + override fun getRefreshKey(state: PagingState): Int? = null - override fun loadInitial( - params: LoadInitialParams, - callback: LoadInitialCallback - ) { - historyProvider.getHistory(INITIAL_OFFSET, params.requestedLoadSize) { history -> - callback.onResult(history.positionWithOffset(INITIAL_OFFSET)) + // params.key is expected to be null for the initial load or a refresh + override suspend fun load(params: LoadParams): LoadResult { + val offset = params.key ?: 0 + val historyItems = historyProvider.getHistory(offset, params.loadSize).run { + positionWithOffset(offset) } - } - - override fun loadAfter(params: LoadParams, callback: LoadCallback) { - historyProvider.getHistory(params.key, params.requestedLoadSize) { history -> - callback.onResult(history.positionWithOffset(params.key)) + val nextOffset = if (historyItems.isEmpty()) { + if (params.key == null) { + onZeroItemsLoaded.invoke() + } + null + } else { + (offset + historyItems.size) + 1 } - } - - override fun loadBefore(params: LoadParams, callback: LoadCallback) { /* noop */ } - - companion object { - internal const val INITIAL_OFFSET = 0 + // prevKey is needed in case load would work upwards, so passing null is fine + return LoadResult.Page( + data = historyItems, + prevKey = null, + nextKey = nextOffset + ) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSourceFactory.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSourceFactory.kt deleted file mode 100644 index 64aeba2299..0000000000 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryDataSourceFactory.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.library.history - -import androidx.lifecycle.MutableLiveData -import androidx.paging.DataSource -import org.mozilla.fenix.components.history.PagedHistoryProvider - -class HistoryDataSourceFactory( - private val historyProvider: PagedHistoryProvider -) : DataSource.Factory() { - val datasource = MutableLiveData() - - override fun create(): DataSource { - val datasource = HistoryDataSource(historyProvider) - this.datasource.postValue(datasource) - return datasource - } -} diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index 478120132e..7d25e8c9c9 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -15,11 +15,13 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope import androidx.navigation.NavDirections import androidx.navigation.fragment.findNavController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.HistoryMetadataAction @@ -158,10 +160,11 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandler { historyView::updateEmptyState ) - model.history.observe( - viewLifecycleOwner, - historyView.historyAdapter::submitList - ) + model.viewModelScope.launch { + model.history.collect { + historyView.historyAdapter.submitData(it) + } + } } } @@ -322,7 +325,7 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandler { requireComponents.core.historyStorage.deleteEverything() deleteOpenTabsEngineHistory(requireComponents.core.store) launch(Main) { - viewModel.invalidate() + historyView.historyAdapter.refresh() historyStore.dispatch(HistoryFragmentAction.ExitDeletionMode) showSnackBar( requireView(), @@ -411,6 +414,6 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandler { private suspend fun syncHistory() { val accountManager = requireComponents.backgroundServices.accountManager accountManager.syncNow(SyncReason.User) - viewModel.invalidate() + historyView.historyAdapter.refresh() } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt index e8c81493da..a95116cdce 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt @@ -61,7 +61,7 @@ class HistoryView( historyAdapter.updatePendingDeletionIds(state.pendingDeletionIds) - updateEmptyState(state.pendingDeletionIds.size != historyAdapter.currentList?.size) + updateEmptyState(state.pendingDeletionIds.size != historyAdapter.itemCount) historyAdapter.updateMode(state.mode) val first = layoutManager.findFirstVisibleItemPosition() diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryViewModel.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryViewModel.kt index f4aa380e49..13ec275413 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryViewModel.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryViewModel.kt @@ -4,33 +4,28 @@ package org.mozilla.fenix.library.history -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.paging.LivePagedListBuilder -import androidx.paging.PagedList +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow import org.mozilla.fenix.components.history.PagedHistoryProvider class HistoryViewModel(historyProvider: PagedHistoryProvider) : ViewModel() { - var history: LiveData> + var history: Flow> var userHasHistory = MutableLiveData(true) - private val datasource: LiveData init { - val historyDataSourceFactory = HistoryDataSourceFactory(historyProvider) - datasource = historyDataSourceFactory.datasource - - history = LivePagedListBuilder(historyDataSourceFactory, PAGE_SIZE) - .setBoundaryCallback(object : PagedList.BoundaryCallback() { - override fun onZeroItemsLoaded() { - userHasHistory.value = false - } - }) - .build() - } - - fun invalidate() { - datasource.value?.invalidate() + history = Pager( + PagingConfig(PAGE_SIZE), + null + ) { + HistoryDataSource( + historyProvider = historyProvider, + onZeroItemsLoaded = { userHasHistory.value = false } + ) + }.flow } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt index e11694804d..743ca290ef 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsExceptionsFragment.kt @@ -17,8 +17,6 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope -import androidx.paging.LivePagedListBuilder -import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -36,6 +34,7 @@ import org.mozilla.fenix.ext.nav private const val MAX_ITEMS_PER_PAGE = 50 +@Suppress("DEPRECATION") class SitePermissionsExceptionsFragment : Fragment(R.layout.fragment_site_permissions_exceptions), View.OnClickListener { private lateinit var emptyContainerMessage: View @@ -63,7 +62,7 @@ class SitePermissionsExceptionsFragment : withContext(Main) { val adapter = ExceptionsAdapter(this@SitePermissionsExceptionsFragment) - val liveData = LivePagedListBuilder(sitePermissionsPaged, MAX_ITEMS_PER_PAGE).build() + val liveData = androidx.paging.LivePagedListBuilder(sitePermissionsPaged, MAX_ITEMS_PER_PAGE).build() liveData.observe( viewLifecycleOwner, @@ -135,8 +134,9 @@ class SitePermissionsExceptionsFragment : class SitePermissionsViewHolder(val view: View, val iconView: ImageView, val siteTextView: TextView) : RecyclerView.ViewHolder(view) +@Suppress("DEPRECATION") class ExceptionsAdapter(private val clickListener: View.OnClickListener) : - PagedListAdapter(diffCallback) { + androidx.paging.PagedListAdapter(diffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SitePermissionsViewHolder { val context = parent.context diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 114522f4e1..2cd7d9e6be 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -35,7 +35,7 @@ object Versions { const val androidx_navigation = "2.3.3" const val androidx_recyclerview = "1.2.1" const val androidx_core = "1.3.2" - const val androidx_paging = "2.1.2" + const val androidx_paging = "3.1.0" const val androidx_transition = "1.4.0" const val androidx_work = "2.7.1" const val androidx_datastore = "1.0.0"