Skip to content

Align iOS + Android to per-collection repository shape#88

Draft
Brian Plattenburg (bplattenburg) wants to merge 14 commits into
BP/sync-group-and-routing-hintfrom
bp/per-collection-repositories
Draft

Align iOS + Android to per-collection repository shape#88
Brian Plattenburg (bplattenburg) wants to merge 14 commits into
BP/sync-group-and-routing-hintfrom
bp/per-collection-repositories

Conversation

@bplattenburg

Copy link
Copy Markdown
Member

Stacked on #80. Aligns iOS + Android to a per-collection repository shape.

Shape

  • DittoManager (singleton on both platforms) — owns the Ditto SDK, transport config, routing, permissions.
  • Three repos per platformOrdersRepository (subscription + mutations + eviction), SaleItemsRepository, LocationsRepository.
  • LocationsRepository is the active-location source of truth. Its setActiveLocation is the only writer — persists, publishes via @Published / Flow, applies routing. The other repos subscribe to its stream and re-register their own subscriptions when it changes. No orchestrator; dependencies go one way.
  • View models own derived UI state (formatted strings, layout decisions, button labels). Views render.

DI

  • Android: Hilt @Singleton + constructor injection.
  • iOS: only DittoManager is .shared. Repositories owned by the App struct via @StateObject, passed via .environmentObject(...).

Worth scrutinizing

  • Both: LocationsRepository mixes "synced collection" with "active-location state."
  • iOS: repositories aren't singletons.
  • Both: cold start is implicit — LocationsRepository.init restores the persisted id, downstream repos react via their subscriptions.

Split the monolithic DittoRepository into three collection-focused
repositories (Orders, SaleItems, Locations), each owning its own
subscription and DQL. Eviction is collection-specific and moves into
OrdersRepository. LocationsRepository filters to demo location IDs
defensively, matching iOS behavior.

DittoRepository remains as a thin delegator for now to keep consumers
unchanged; it is retired in a follow-up commit once view models and
use cases migrate to the specific repositories.
…ository

Introduce SetActiveLocationUseCase as the single point that switches
location: it persists the locationId, reconfigures Ditto's routing,
and re-registers the orders + sale_items subscriptions. Replaces the
old SetCurrentLocationUseCase + DittoRepository flow-collector pattern.

Cold start orchestration moves into DittoPOSApplication.onCreate:
start the locations subscription, seed + evict, then activate the
persisted location.

View models and Ditto-instance/permission use cases now depend on the
specific repositories or DittoManager directly, no longer on
DittoRepository.
DittoManager mirrors Android's DittoManager (`ditto-wrapper` module):
it owns the Ditto instance, transport config, sync start/stop, and
now also the sync-group / routing-hint configuration. DittoService
asks the manager to apply routing when the active location changes
rather than touching the Ditto SDK directly.
Mirrors the Android shape. DittoManager owns the SDK instance and
transport-level routing config. Three repositories — Orders, SaleItems,
Locations — own their collection's subscription, observers, and
mutations. LocationsRepository also holds the currently-selected
location id and the derived current location (initialized from the
persisted Settings value on launch).

SetActiveLocationUseCase is the single entry point for switching
location: it persists, reconfigures routing, and re-registers the
orders + sale_items subscriptions. Cold start runs from
`DittoPOSApp.task`: seed demo data, run eviction (now on
OrdersRepository), then activate the persisted location.

Views and view models get their repositories via @EnvironmentObject;
view models that need refs accept them via init. The willUpdate
notification used to chain LocationsView -> POS_VM is gone; POS_VM
now reacts to LocationsRepository.$currentLocation with `scan` to
both reset the outgoing order and start a fresh one at the new
location.

POSOrderVM and POSOrderTotalVM were folded into their views as
computed properties of the shared POS_VM. KDS_VM and KDSOrderVM
take OrdersRepository via init.
- POS_VM -> POSViewModel (file + class)
- Existing layout-only POSViewModel -> POSLayoutViewModel
- KDS_VM -> KDSViewModel (file + class)
- KDSOrderVM -> KDSOrderViewModel
- Property renames: vm/posVM -> viewModel/layoutViewModel
- Local renames: svr -> proxy, f -> formatter, s -> status, ts -> timestamp
- Drop unused Fixtures.createdAtStr
POSViewModel exposes orderTitle, orderItems, orderTotalDisplay,
orderIsPaid, orderIsEmpty, actionsDisabled, payButtonLabel, and
scrollToBottomConfig(). KDSOrderViewModel exposes headerText,
statusColor, statusTitle, summaryEntries, isPaid. Views now render
those values rather than computing them inline. Brings iOS closer
to Android's pre-derived `uiState` pattern.
LocationsRepository becomes the single source of truth and the only
writer for the active location on both platforms. setActiveLocation
persists the id and applies routing config via DittoManager.
OrdersRepository and SaleItemsRepository subscribe to the locations
repository's location-id stream in init and re-register their own
collection subscriptions on every change.

The orchestration use case is gone — UI calls
locationsRepository.setActiveLocation directly. Cold start happens
automatically: LocationsRepository's init self-restores the persisted
id (synchronously on iOS, via a coroutine on Android), which emits
through the flow/publisher and downstream repositories react.

Dependencies flow one direction: UI -> LocationsRepository, other
repos -> LocationsRepository. No cross-repo coupling, no orchestrator.
Owns selectedTab (initial value + persistence), presentSettingsView,
and the navbar title. Subscribes to LocationsRepository so picking a
location auto-switches to the POS tab and the title follows the
active location. ensureLocationSelected() handles the onAppear
"no location -> show Locations tab" branch.

MainView becomes a render layer over the VM. The view takes
LocationsRepository via init (the App constructs it) and creates
the VM via StateObject(wrappedValue:) — same pattern POSView and
KDSOrdersGridView use.
POSLayoutViewModel gains itemSide(for:) and gridColumns(for:),
parameterized on UserInterfaceSizeClass. POSGridView grabs the
layout VM from the environment (alongside the data POSViewModel)
and asks it for sizes — no more inline size-class branching in the
view. Pure functions, testable.
Selection state, derivation, and the "ignore no-op selection" guard
move out of LocationsView into a dedicated VM. View just renders the
list against viewModel.locations and binds selection to
viewModel.selectionBinding.
Matches iOS's MainViewModel naming. The Android class was already
the equivalent of a "main activity view model" — owns app-level
uiState (currentLocationName, appConfigurationState), drives the
shell scaffolding. "Main" reads more naturally than "Core" and
makes cross-platform pairing obvious.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant