From 1062129831911b285f10cdc61b8850ccec9442b1 Mon Sep 17 00:00:00 2001 From: MatthewTighe Date: Thu, 6 Jul 2023 10:48:14 -0700 Subject: [PATCH] Bug 1839984 - Update architecture example to use lib-state --- docs/architecture-overview.md | 22 +++--- .../architectureexample/ContactsController.kt | 24 ------- docs/architectureexample/ContactsFragment.kt | 47 ------------- .../architectureexample/ContactsInteractor.kt | 23 ------- docs/architectureexample/ContactsStore.kt | 47 ------------- docs/architectureexample/ContactsView.kt | 36 ---------- .../HistoryFragmentExample.kt | 60 ++++++++++++++++ .../HistoryNavigationMiddlewareExample.kt | 29 ++++++++ .../HistoryStorageMiddlewareExample.kt | 39 +++++++++++ .../HistoryStoreExample.kt | 64 ++++++++++++++++++ .../HistoryTelemetryMiddlewareExample.kt | 22 ++++++ docs/architectureexample/ThemeController.kt | 14 ---- .../example-app-wireframe.png | Bin 20439 -> 0 bytes 13 files changed, 223 insertions(+), 204 deletions(-) delete mode 100644 docs/architectureexample/ContactsController.kt delete mode 100644 docs/architectureexample/ContactsFragment.kt delete mode 100644 docs/architectureexample/ContactsInteractor.kt delete mode 100644 docs/architectureexample/ContactsStore.kt delete mode 100644 docs/architectureexample/ContactsView.kt create mode 100644 docs/architectureexample/HistoryFragmentExample.kt create mode 100644 docs/architectureexample/HistoryNavigationMiddlewareExample.kt create mode 100644 docs/architectureexample/HistoryStorageMiddlewareExample.kt create mode 100644 docs/architectureexample/HistoryStoreExample.kt create mode 100644 docs/architectureexample/HistoryTelemetryMiddlewareExample.kt delete mode 100644 docs/architectureexample/ThemeController.kt delete mode 100644 docs/architectureexample/example-app-wireframe.png diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md index 9fc12419d..f0ef8f184 100644 --- a/docs/architecture-overview.md +++ b/docs/architecture-overview.md @@ -111,21 +111,17 @@ In some cases, it can be appropriate to initiate side-effects from the view when ------- ## Simplified Example -When reading through live code trying to understand an architecture, it can be difficult to find canonical examples, and often hard to locate the most important aspects. This is a simplified example using a hypothetical app that should help clarify the above patterns. +When reading through live code trying to understand an architecture, it can be difficult to find canonical examples, and often hard to locate the most important aspects. This is a simplified example of a basic history screen that includes a list of history items and which can be opened, multi-selected, and deleted. -![example app wireframe](./architectureexample/example-app-wireframe.png?raw=true) +The following are links to the example versions of the architectural components listed above. -This app currently has three (wonderful) features. -- Clicking on one of the colored circles will update the toolbar color -- Clicking on 'Rename', typing a new name, and selecting return will update the name of the contact -- Clicking anywhere else on a contact will navigate to a text message fragment - -These link to the architectural code that accomplishes those features: -- [ContactsView](./architectureexample/ContactsView.kt) -- [ContactsStore](./architectureexample/ContactsStore.kt) -- [ContactsState](./architectureexample/ContactsStore.kt) -- [ContactsReducer](./architectureexample/ContactsStore.kt) -- [ContactsFragment](./architectureexample/ContactsFragment.kt) +- [HistoryFragment](./architectureexample/HistoryFragmentExample.kt) +- [HistoryStore](./architectureexample/HistoryStoreExample.kt) +- [HistoryState](./architectureexample/HistoryStoreExample.kt) +- [HistoryReducer](./architectureexample/HistoryStoreExample.kt) +- [HistoryNavigationMiddleware](./architectureexample/HistoryNavigationMiddlewareExample.kt) +- [HistoryStorageMiddleware](./architectureexample/HistoryStorageMiddlewareExample.kt) +- [HistoryTelemetryMiddleware](./architectureexample/HistoryTelemetryMiddlewareExample.kt) ------- diff --git a/docs/architectureexample/ContactsController.kt b/docs/architectureexample/ContactsController.kt deleted file mode 100644 index e9274ce73..000000000 --- a/docs/architectureexample/ContactsController.kt +++ /dev/null @@ -1,24 +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/. */ - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ContactsController( - private val store: ContactsStore, - private val navController: NavController, -) { - - fun contactRenamed(contactId: Int, newName: String) { - store.dispatch(ContactsAction.ContactRenamed(contactId = contactId, newName = newName)) - } - - fun chatSelected(contactId: Int) { - // This is how we pass arguments between fragments using Google's navigation library. - // See https://developer.android.com/guide/navigation/navigation-getting-started - val directions = ContactsFragment.actionContactsFragmentToChatFragment( - contactId = contactId, - ) - navController.nav(R.id.contactFragment, directions) - } -} diff --git a/docs/architectureexample/ContactsFragment.kt b/docs/architectureexample/ContactsFragment.kt deleted file mode 100644 index d72753233..000000000 --- a/docs/architectureexample/ContactsFragment.kt +++ /dev/null @@ -1,47 +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/. */ - - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ContactsFragment : Fragment() { - - lateinit var contactsStore: ContactsStore - lateinit var contactsView: ContactsView - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_contacts, container, false) - - // Create the various components and hook them up to each other - val initialState = ContactsState( - contacts = emptyList(), - theme = Theme.ORANGE - ) - - contactsStore = ContactsStore(initialState = initialState) - - val contactsController = ContactsController( - store = store, - navController = findNavController() - ) - - val themeController = ThemeController( - store = store - ) - - val interactor = ContactsInteractor( - contactsController = contactsController, - themeController = themeController - ) - - contactsView = ContactsView(view.contains_container, interactor) - } - - override onViewCreated(view: View, savedInstanceState: Bundle?) { - // Whenever State is updated, pass it to the View - consumeFrom(contactsStore) { state -> - contactsView.update(state) - } - } -} diff --git a/docs/architectureexample/ContactsInteractor.kt b/docs/architectureexample/ContactsInteractor.kt deleted file mode 100644 index 02bc65c94..000000000 --- a/docs/architectureexample/ContactsInteractor.kt +++ /dev/null @@ -1,23 +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/. */ - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ContactsInteractor( - private val contactsController: ContactsController, - private val themeController: ThemeController, -) { - - fun onThemeSelected(theme: Theme) { - themeController.themeSelected(theme) - } - - fun onContactRenamed(contactId: Int, newName: String) { - contactsController.contactRenamed(contactId, newName) - } - - fun onChatSelected(contactId: Int) { - contactsController.chatSelected(contactId) - } -} diff --git a/docs/architectureexample/ContactsStore.kt b/docs/architectureexample/ContactsStore.kt deleted file mode 100644 index 7231d7b05..000000000 --- a/docs/architectureexample/ContactsStore.kt +++ /dev/null @@ -1,47 +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/. */ - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ContactsStore( - private val initialState: ContactsState, -) : Store>(initialState, ::reducer) - -sealed class ContactsAction { - data class ContactRenamed(val contactId: Int, val newName: String) : ContactsAction - data class ThemeChanged(val newTheme: Theme) : ContactsAction -} - -data class ContactsState( - val contacts: List, - val theme: Theme, -) - -data class Contact( - val name: String, - val id: Int, - val imageUrl: Uri, -) - -enum class Theme { - ORANGE, DARK -} - -fun reducer(oldState: ContactsState, action: ContactsAction): ContactsState = when (action) { - is ContactsAction.ThemeChanged -> oldState.copy(theme = action.newTheme) - is ContactsAction.ContactRenamed -> { - val newContacts = oldState.contacts.map { contact -> - // If this is the contact we want to change... - if (contact.id == action.contactId) { - // Update its name, but keep other values the same - contact.copy(name = newName) - } else { - // Otherwise return the original contact - return@map contact - } - } - - return oldState.copy(contacts = newContacts) - } -} diff --git a/docs/architectureexample/ContactsView.kt b/docs/architectureexample/ContactsView.kt deleted file mode 100644 index a6a7dc872..000000000 --- a/docs/architectureexample/ContactsView.kt +++ /dev/null @@ -1,36 +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/. */ - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ContactsView( - private val container: ViewGroup, - private val interactor: ContactsInteractor, -) { - - val view: View = LayoutInflater.from(container.context) - .inflate(R.layout.contact_list, container, true) - - private val contactAdapter: ContactAdapter - - init { - // Setup view constraints and anything else that will not change as data updates - view.select_theme_orange.setOnClickListener { - interactor.onThemeSelected(Theme.ORANGE) - } - view.select_theme_dark.setOnClickListner { - interactor.onThemeSelected(Theme.DARK) - } - // The RecyclerView.Adapter is passed the interactor, and will call it from its own listeners - contactAdapter = ContactAdapter(view.contactRoot, interactor) - view.contact_recycler.apply { - adapter = contactAdapter - } - } - - fun update(state: ContactsState) { - view.toolbar.setColor(ContextCompat.getColor(this, R.color.state.toolbarColor)) - contactAdapter.update(state) - } -} diff --git a/docs/architectureexample/HistoryFragmentExample.kt b/docs/architectureexample/HistoryFragmentExample.kt new file mode 100644 index 000000000..f3fa6e3ee --- /dev/null +++ b/docs/architectureexample/HistoryFragmentExample.kt @@ -0,0 +1,60 @@ +/* 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/. */ + +// This is example code for the 'Simplified Example' section of +// /docs/architecture-overview.md +class HistoryFragment : Fragment() { + + private val store by lazy { + StoreProvider.get(this) { + HistoryStore( + initialState = HistoryState.initial, + middleware = listOf( + HistoryNavigationMiddleware(findNavController()) + HistoryStorageMiddleware(HistoryStorage()), + HistoryTelemetryMiddleware(), + ) + ) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return ComposeView(requireContext()).apply { + setContent { + HistoryScreen(store) + } + } + } +} + +@Composable +private fun HistoryScreen(store: HistoryStore) { + val state = store.observeAsState(initialValue = HistoryState.initial) { state -> state } + val listState = rememberLazyListState() + LazyColumn(listState) { + if (state.selectedItems.isNotEmpty()) { + HistoryMultiSelectHeader( + onDeleteSelectedClick = { + store.dispatch(HistoryAction.DeleteItems(state.selectedItems)) + } + ) + } else { + HistoryHeader( + onDeleteAllClick = { store.dispatch(HistoryAction.DeleteItems(state.items)) } + ) + } + items(items = state.displayItems, key = { item -> item.id } ) { item -> + val isSelected = state.selectedItems.find { selectedItem -> + selectdItem == item + } + HistoryItem( + item = item, + isSelected = isSelected, + onClick = { store.dispatch(HistoryAction.OpenItem(item)) }, + onLongClick = { store.dispatch(HistoryAction.ToggleItemSelection(item)) }, + onDeleteClick = { store.dispatch(HistoryAction.DeleteItems(listOf(item))) }, + ) + } + } +} diff --git a/docs/architectureexample/HistoryNavigationMiddlewareExample.kt b/docs/architectureexample/HistoryNavigationMiddlewareExample.kt new file mode 100644 index 000000000..49f3d42f2 --- /dev/null +++ b/docs/architectureexample/HistoryNavigationMiddlewareExample.kt @@ -0,0 +1,29 @@ +/* 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/. */ + +// This is example code for the 'Simplified Example' section of +// /docs/architecture-overview.md +class HistoryNavigationMiddleware( + private val navController: NavController, +) : Middleware { + override fun invoke( + context: MiddlewareContext, + next: (HistoryAction) -> Unit, + action: HistoryAction, + ) { + // This middleware won't need to manipulate the action, so the action can be passed through + // the middleware chain before the side-effects are initiated + next(action) + when(action) { + is HistoryAction.OpenItem -> { + navController.openToBrowserAndLoad( + searchTermOrURL = item.url, + newTab = true, + from = BrowserDirection.FromHistory, + ) + } + else -> Unit + } + } +} diff --git a/docs/architectureexample/HistoryStorageMiddlewareExample.kt b/docs/architectureexample/HistoryStorageMiddlewareExample.kt new file mode 100644 index 000000000..674adca85 --- /dev/null +++ b/docs/architectureexample/HistoryStorageMiddlewareExample.kt @@ -0,0 +1,39 @@ +/* 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/. */ + +// This is example code for the 'Simplified Example' section of +// /docs/architecture-overview.md +class HistoryStorageMiddleware( + private val storage: HistoryStorage + private val scope: CoroutineScope, +) : Middleware { + override fun invoke( + context: MiddlewareContext, + next: (HistoryAction) -> Unit, + action: HistoryAction, + ) { + // This middleware won't need to manipulate the action, so the action can be passed through + // the middleware chain before the side-effects are initiated + next(action) + when(action) { + is HistoryAction.Init -> { + scope.launch { + val history = storage.load() + context.store.dispatch(HistoryAction.ItemsChanged(history)) + } + } + is HistoryAction.DeleteItems -> { + scope.launch { + val currentItems = context.state.items + if (storage.delete(action.items) is HistoryStorage.Success) { + context.store.dispatch( + HistoryAction.DeleteFinished() + ) + } + } + } + else -> Unit + } + } +} diff --git a/docs/architectureexample/HistoryStoreExample.kt b/docs/architectureexample/HistoryStoreExample.kt new file mode 100644 index 000000000..c53d8d298 --- /dev/null +++ b/docs/architectureexample/HistoryStoreExample.kt @@ -0,0 +1,64 @@ +/* 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/. */ + +// This is example code for the 'Simplified Example' section of +// /docs/architecture-overview.md +class HistoryStore( + private val initialState: HistoryState, + private val middleware: List> +) : Store>(initialState, middleware, ::reducer) { + init { + // This will ensure that middlewares can take any actions they need to during initialization + dispatch(HistoryAction.Init) + } +} + +sealed class HistoryAction { + object Init : HistoryAction() + data class ItemsChanged(val items: List) : HistoryAction() + data class DeleteItems(val items: List) : HistoryAction() + data class DeleteFinished() : HistoryAction() + data class ToggleItemSelection(val item: History) : HistoryAction() + data class OpenItem(val item: History) : HistoryAction() +} + +data class HistoryState( + val items: List, + val selectedItems: List, + val itemsBeingDeleted: List, + companion object { + val initial = HistoryState( + items = listOf(), + selectedItems = listOf(), + itemsBeingDeleted = listOf(), + ) + } +) { + val displayItems = items.filter { item -> + item !in itemsBeingDeleted + } +} + +fun reducer(oldState: HistoryState, action: HistoryAction): HistoryState = when (action) { + is HistoryAction.ItemsChanged -> oldState.copy(items = action.items) + is HistoryAction.DeleteItems -> oldState.copy(itemsBeingDeleted = action.items) + is HistoryAction.DeleteFinished -> oldState.copy( + items = oldState.items - oldState.itemsBeingDeleted, + itemsBeingDeleted = listOf(), + ) + is HistoryAction.ToggleItemSelection -> { + if (oldState.selectedItems.contains(action.item)) { + oldState.copy(selectedItems = oldState.selectedItems - action.item) + } else { + oldState.copy(selectedItems = oldState.selectedItems + action.item) + } + } + else -> Unit +} + +data class History( + val id: Int, + val title: String, + val url: Uri, +) diff --git a/docs/architectureexample/HistoryTelemetryMiddlewareExample.kt b/docs/architectureexample/HistoryTelemetryMiddlewareExample.kt new file mode 100644 index 000000000..9bda08cee --- /dev/null +++ b/docs/architectureexample/HistoryTelemetryMiddlewareExample.kt @@ -0,0 +1,22 @@ +/* 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/. */ + +// This is example code for the 'Simplified Example' section of +// /docs/architecture-overview.md +class HistoryTelemetryMiddleware : Middleware { + override fun invoke( + context: MiddlewareContext, + next: (HistoryAction) -> Unit, + action: HistoryAction, + ) { + // This middleware won't need to manipulate the action, so the action can be passed through + // the middleware chain before the side-effects are initiated + next(action) + when(action) { + is HistoryAction.DeleteItems -> History.itemsDeleted.record() + is HistoryAction.OpenItem -> History.itemOpened.record() + else -> Unit + } + } +} diff --git a/docs/architectureexample/ThemeController.kt b/docs/architectureexample/ThemeController.kt deleted file mode 100644 index d4677e8e1..000000000 --- a/docs/architectureexample/ThemeController.kt +++ /dev/null @@ -1,14 +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/. */ - - -// This is example code for the 'Simplified Example' section of -// /docs/architecture-overview.md -class ThemeController( - private val ContactsStore -) { - fun themeSelected(newTheme: Theme) { - store.dispatch(ContactsAction.ThemeChanged(newTheme = newTheme)) - } -} diff --git a/docs/architectureexample/example-app-wireframe.png b/docs/architectureexample/example-app-wireframe.png deleted file mode 100644 index b1566d9d457af0c35037881f88b0fa627a912239..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20439 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 E00Yw99{>OV