From edb0a3ed08768ecca58b973d2fec0a88670b5f6c Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Tue, 16 Jul 2019 14:29:57 -0400 Subject: [PATCH] For #3633 - Update StoreProvider to use a callback Co-authored-by: Christian Sadilek --- .../mozilla/fenix/components/StoreProvider.kt | 38 ++++++---- .../fenix/library/history/HistoryFragment.kt | 5 +- .../mozilla/fenix/search/SearchFragment.kt | 22 +++--- .../fenix/components/StoreProviderTest.kt | 73 +++++++++++++++++++ 4 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/components/StoreProviderTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/components/StoreProvider.kt b/app/src/main/java/org/mozilla/fenix/components/StoreProvider.kt index 6071ebb9d..7a00759c0 100644 --- a/app/src/main/java/org/mozilla/fenix/components/StoreProvider.kt +++ b/app/src/main/java/org/mozilla/fenix/components/StoreProvider.kt @@ -9,25 +9,37 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.get -import mozilla.components.lib.state.Action -import mozilla.components.lib.state.State import mozilla.components.lib.state.Store /** - * Generic ViewModel to wrap a State object for state restoration + * Generic ViewModel to wrap a State object for state restoration. + * + * @property store [Store] instance attached to [ViewModel]. */ -@Suppress("UNCHECKED_CAST") -class StoreProvider>(val store: T) : ViewModel() { - companion object { - fun > get(fragment: Fragment, initialStore: T): T { - val factory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): VM { - return StoreProvider(initialStore) as VM - } - } +class StoreProvider>( + val store: T +) : ViewModel() { - val viewModel: StoreProvider = ViewModelProviders.of(fragment, factory).get() + companion object { + fun > get(fragment: Fragment, createStore: () -> T): T { + val factory = StoreProviderFactory(createStore) + val viewModel: StoreProvider = ViewModelProviders.of(fragment, factory).get() return viewModel.store } } } + +/** + * ViewModel factory to create [StoreProvider] instances. + * + * @param createStore Callback to create a new [Store], used when the [ViewModel] is first created. + */ +class StoreProviderFactory>( + private val createStore: () -> T +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): VM { + return StoreProvider(createStore()) as VM + } +} 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 b30844a35..838f2c079 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 @@ -55,14 +55,13 @@ class HistoryFragment : Fragment(), BackHandler { savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_history, container, false) - historyStore = StoreProvider.get( - this, + historyStore = StoreProvider.get(this) { HistoryStore( HistoryState( items = listOf(), mode = HistoryState.Mode.Normal ) ) - ) + } historyInteractor = HistoryInteractor( historyStore, ::openItem, diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index b8f2cb6d8..f1d4bc56d 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -72,20 +72,20 @@ class SearchFragment : Fragment(), BackHandler { val view = inflater.inflate(R.layout.fragment_search, container, false) val url = session?.url ?: "" - searchStore = StoreProvider.get( - this, + searchStore = StoreProvider.get(this) { SearchStore( SearchState( - query = url, - showShortcutEnginePicker = false, - searchEngineSource = SearchEngineSource.Default( - requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()) - ), - showSuggestions = Settings.getInstance(requireContext()).showSearchSuggestions, - showVisitedSitesBookmarks = Settings.getInstance(requireContext()).shouldShowVisitedSitesBookmarks, - session = session) + query = url, + showShortcutEnginePicker = false, + searchEngineSource = SearchEngineSource.Default( + requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()) + ), + showSuggestions = Settings.getInstance(requireContext()).showSearchSuggestions, + showVisitedSitesBookmarks = Settings.getInstance(requireContext()).shouldShowVisitedSitesBookmarks, + session = session + ) ) - ) + } searchInteractor = SearchInteractor( activity as HomeActivity, diff --git a/app/src/test/java/org/mozilla/fenix/components/StoreProviderTest.kt b/app/src/test/java/org/mozilla/fenix/components/StoreProviderTest.kt new file mode 100644 index 000000000..20b25fe39 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/StoreProviderTest.kt @@ -0,0 +1,73 @@ +/* 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.components + +import androidx.fragment.app.Fragment +import androidx.fragment.app.testing.launchFragmentInContainer +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.TestApplication +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) +class StoreProviderTest { + + private class BasicState : State + + private val basicStore = Store(BasicState()) { state, _: Action -> state } + + @Test + fun `factory returns store provider`() { + var createCalled = false + val factory = StoreProviderFactory { + createCalled = true + basicStore + } + + assertFalse(createCalled) + + assertEquals(basicStore, factory.create(StoreProvider::class.java).store) + + assertTrue(createCalled) + } + + @Test + fun `get returns store`() { + val scenario = launchFragmentInContainer { Fragment() } + scenario.onFragment { + val store = StoreProvider.get(it) { basicStore } + assertEquals(basicStore, store) + } + } + + @Test + fun `get only calls createStore if needed`() { + val scenario = launchFragmentInContainer { Fragment() } + var createCalled = false + val createStore = { + createCalled = true + basicStore + } + + scenario.onFragment { + StoreProvider.get(it, createStore) + } + assertTrue(createCalled) + + createCalled = false + scenario.onFragment { + StoreProvider.get(it, createStore) + } + assertFalse(createCalled) + } +}