From f5cba6e676bf61923296d8b6f314e493eb2090b3 Mon Sep 17 00:00:00 2001 From: Marc Leclair Date: Tue, 16 Jun 2020 23:44:44 -0400 Subject: [PATCH 1/2] For #11660:added prefetch for topsites TopSites will be prefetched with observerOnce (wrapper around observerForever). Also, the SessionControlView.update() is called right away instead of waiting from consumeFrom in the HomeFragment.onCreateView() which will allow the UI to render all at once on its first perform traversal --- .../org/mozilla/fenix/FenixApplication.kt | 8 ++++++++ .../fenix/components/TopSiteStorage.kt | 7 +++++++ .../java/org/mozilla/fenix/ext/LiveData.kt | 20 +++++++++++++++++++ .../org/mozilla/fenix/home/HomeFragment.kt | 6 ++++++ 4 files changed, 41 insertions(+) create mode 100644 app/src/main/java/org/mozilla/fenix/ext/LiveData.kt diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 48956ca458d9..3fa3a10e9237 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -142,6 +142,7 @@ open class FenixApplication : LocaleAwareApplication() { } } + prefetchForHomeFragment() setupLeakCanary() if (settings().isTelemetryEnabled) { components.analytics.metrics.start(MetricServiceType.Data) @@ -228,6 +229,13 @@ open class FenixApplication : LocaleAwareApplication() { // no-op, LeakCanary is disabled by default } + // This is for issue https://github.com/mozilla-mobile/fenix/issues/11660. We prefetch our info for startup + // so that we're sure that we have all the data available as our fragment is launched. + private fun prefetchForHomeFragment() { + StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.core.topSiteStorage.prefetch() + } + } private fun setupPush() { // Sets the PushFeature as the singleton instance for push messages to go to. // We need the push feature setup here to deliver messages in the case where the service diff --git a/app/src/main/java/org/mozilla/fenix/components/TopSiteStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TopSiteStorage.kt index 0922a0606b5c..a9ddaacee23c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TopSiteStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TopSiteStorage.kt @@ -14,6 +14,7 @@ import mozilla.components.feature.top.sites.TopSite import mozilla.components.feature.top.sites.TopSiteStorage import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.R +import org.mozilla.fenix.ext.observeOnce import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale @@ -86,4 +87,10 @@ class TopSiteStorage(private val context: Context) { context.settings().defaultTopSitesAdded = true } } + + fun prefetch() { + getTopSites().observeOnce { + cachedTopSites = it + } + } } diff --git a/app/src/main/java/org/mozilla/fenix/ext/LiveData.kt b/app/src/main/java/org/mozilla/fenix/ext/LiveData.kt new file mode 100644 index 000000000000..718385219bd1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ext/LiveData.kt @@ -0,0 +1,20 @@ +/* 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.ext + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer + +/** + * Observe a LiveData once and unregister from it as soon as the live data returns a value + */ +fun LiveData.observeOnce(observer: (T) -> Unit) { + observeForever(object : Observer { + override fun onChanged(value: T) { + removeObserver(this) + observer(value) + } + }) +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 1ac5e78a2144..a0ff76bedda5 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -215,6 +215,12 @@ class HomeFragment : Fragment() { sessionControlInteractor, homeViewModel ) + + // This has to be called separately from the consumeFrom block since the coroutine doesn't + // allow our UI to be updated right away and delays our start up. The init block allows us to + // force our UI to render with the data available in our store at fragment creation time. + sessionControlView?.update(homeFragmentStore.state) + activity.themeManager.applyStatusBarTheme(activity) view.consumeFrom(homeFragmentStore, viewLifecycleOwner) { From b78ae245ff164556e121cc75a59dc1900975292f Mon Sep 17 00:00:00 2001 From: Marc Leclair Date: Wed, 17 Jun 2020 19:22:59 -0400 Subject: [PATCH 2/2] Removed the submitList(null) since it retriggered a drawing on lower end device --- .../org/mozilla/fenix/home/HomeFragment.kt | 23 ++++++++++++------- .../home/sessioncontrol/SessionControlView.kt | 6 ----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index a0ff76bedda5..7fb414bdda08 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -216,17 +216,10 @@ class HomeFragment : Fragment() { homeViewModel ) - // This has to be called separately from the consumeFrom block since the coroutine doesn't - // allow our UI to be updated right away and delays our start up. The init block allows us to - // force our UI to render with the data available in our store at fragment creation time. - sessionControlView?.update(homeFragmentStore.state) + updateSessionControlView(view) activity.themeManager.applyStatusBarTheme(activity) - view.consumeFrom(homeFragmentStore, viewLifecycleOwner) { - sessionControlView?.update(it) - } - view.consumeFrom(requireComponents.core.store, viewLifecycleOwner) { val tabCount = if (currentMode.getCurrentMode() == Mode.Normal) { it.normalTabs.size @@ -240,6 +233,20 @@ class HomeFragment : Fragment() { return view } + /** + * The [SessionControlView] is forced to update with our current state when we call + * [HomeFragment.onCreateView] in order to be able to draw everything at once with the current + * data in our store. The [View.consumeFrom] coroutine dispatch + * doesn't get run right away which means that we won't draw on the first layout pass. + */ + fun updateSessionControlView(view: View) { + sessionControlView?.update(homeFragmentStore.state) + + view.consumeFrom(homeFragmentStore, viewLifecycleOwner) { + sessionControlView?.update(it) + } + } + private fun updateLayout(view: View) { val shouldUseBottomToolbar = view.context.settings().shouldUseBottomToolbar diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 809b1ec7e62c..f35b8503fda5 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.home.sessioncontrol -import android.os.Build import android.view.View import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -142,13 +141,8 @@ class SessionControlView( } fun update(state: HomeFragmentState) { - // Workaround for list not updating until scroll on Android 5 + 6 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - sessionControlAdapter.submitList(null) - } val stateAdapterList = state.toAdapterList() - if (homeScreenViewModel.shouldScrollToTopSites) { sessionControlAdapter.submitList(stateAdapterList) {