Feat/dev 4038 multiinstance - #210
Merged
Merged
Conversation
Release 2 of multi-instance, storage layer. The default preferences file was a single shared constant, so instances for different shops collided on did/sid. initialize() now derives a per-shop partition name (PreferencesPartition) when the host leaves preferencesKey at its default; a host that passes its own key keeps using it verbatim, with no derivation. To stay transparent, the first init of a shop's partition runs a one-time migration out of the old shared file: it copies every entry only while the partition is still empty and only when the legacy file belongs to that shop (its stored shop_id matches, or is absent for a very old install). An existing single-instance install keeps its identity after upgrading; a partition for a different shop stays clean. No public API change (preferencesKey keeps its default and meaning) and no behaviour change for a single-instance host beyond the file it reads. Covered by PreferencesPartitionTest and PreferencesDataSourceMigrationTest (11 tests, migration guards mutation-checked). Not wired into the demo.
…registerShops Release 3 of multi-instance: open the unified public entry point. A host no longer keeps its own SDK reference — it initializes (or registers) shops through Rees46 and reaches them by shopId. - Rees46.initialize(context, Rees46Config) creates and registers an instance. - Rees46.registerShops(context, configs, eagerInit=false) records shops and initializes them lazily on first getInstance (the region case), or up front when eagerInit is true (the super-shop case). - Rees46.getInstance(shopId?) returns the instance; with no shopId it returns the single one, throwing AmbiguousSdkInstanceException when several shops are registered and SdkNotInitializedException when nothing matches. Instance state was already isolated: initialize() builds a fresh Dagger graph per instance and storage is partitioned per shop (Release 2), so this layer is routing on top of SdkRegistry plus a pending-config map for lazy init. The resolution rules live in a side-effect-free InstanceResolver so every branch is unit-tested without constructing an SDK. SDK.initialize is now @deprecated with a ReplaceWith to Rees46.initialize; it still works and registers the same way. No demo changes. 27 new tests (InstanceResolverTest, Rees46Test), resolver mutation-checked.
Adds an event-driven way to resolve an instance once it exists, instead of the
host holding a reference or the UI polling with retries.
- SdkRegistry.onNextRegister(shopId?) subscribes to the next matching register
and returns a Cancellable; register() wakes matching awaiters.
- Rees46.awaitInstance(shopId?) { } delivers the instance immediately when it is
already live (or materializes a pending registration), otherwise once it is
registered; throws AmbiguousSdkInstanceException for a null shopId with several
live shops. Rees46.isInitialized(shopId?) is the synchronous check.
- StoriesWidget gains an overload that takes shopId (default = the single
instance) and resolves its SDK through awaitInstance, rendering nothing until
ready and cancelling on dispose. The old StoriesWidget(sdk, ...) is @deprecated
but still works — the demo compiles unchanged (deprecation warning only).
The deeper cleanup — StoriesView owning its load so initializeStoriesView and
StoriesManager's single held view reference go away — is a follow-up. 12 notifier
tests, fire path mutation-checked. No demo changes.
…ld view
StoriesManager held a single view reference and pushed loaded stories into it,
so two blocks on one screen fought over that reference — only the last one
registered received data (the empty-pane bug). The view now owns its load.
- StoriesManager is stateless: loadStories(code) { } fetches and parses, no view
held. The only retained reference is a WeakReference to the last attached view,
used solely as the surface for the view-less SDK.showStories(code) — not for
loading.
- StoriesView self-loads on attach: it resolves its instance via
Rees46.awaitInstance(shopId) (new app:shop_id attribute, default = the single
instance) and loads its own block, cancelling the subscription on detach.
- initializeStoriesView keeps working (now @deprecated): it calls
StoriesView.attach, which wires an explicit SDK and reloads, superseding any
pending self-load so the two never double-load. The Compose wrapper uses the
same attach path.
Verified on device: the Legacy and Compose panes now each load independently
(two /stories requests, the Legacy pane populated without the reload workaround),
and tapping a story still opens the full-screen viewer. 4 StoriesManager load
tests. No demo changes.
Release 4 of multi-instance. Rees46.handlePush(payload, event) resolves the target instance from the payload's shop_id and dispatches a RECEIVED (track received + message listener) or CLICKED (track clicked + click action) event. The rule lives in a side-effect-free PushTargetResolver: shop_id names the instance; with no shop_id a single-instance app still resolves; an unknown shop or an absent shop_id with several live shops drops the push instead of delivering it to the wrong one. The SDK's own messaging entry (SDK.onMessage) now resolves by shop_id too, keeping the current-instance fallback so single-instance apps and cold-start delivery are unchanged. Push-token registration already fans out to every instance, which is the super-shop token-sync behaviour. 10 tests (PushTargetResolver mutation-checked; handlePush routing verified via the message listener on bare instances). No demo changes.
…hem by hand Google Play reports the SDK under "Improve your app's performance with bitmap image optimisation", pointing at NotificationImageHelper.loadBitmaps: it fetched every push image with URL.openStream() and decoded it with BitmapFactory, so the bitmap was allocated at the source resolution — a 4000x3000 product photo costs ~46 MB of heap for a block that is 150dp tall, and that bitmap then travels to the system UI through a Binder transaction. Glide is already a dependency (stories, in-app), so nothing new is pulled in. It decodes straight into the box the notification actually shows (screen width by 150dp, capped at 1080px), keeps the decode in software since RemoteViews cannot take a hardware bitmap, and caches the download — the notification is rebuilt on every arrow tap, which used to re-fetch all the images. The load now times out after 15s instead of holding NotificationService open on a stalled CDN, and the result is copied out before the request is cleared so the pooled bitmap cannot be handed to the next decode while the posted notification still draws it. loadBitmaps takes a Context now; both call sites are in the SDK. Empty entries in the comma-separated list are dropped instead of requested, and hasError is derived from the results instead of written by racing coroutines. The demo app posted its BigPicture notification the same manual way, so it gets the same treatment — it is what integrators copy.
SDK: - OnShopMessageListener + process-global shop-aware message routing - Rees46 / SdkRegistry / InstanceResolver refinements - per-shop preferences partition and session isolation (UserSettings, RegisterManager) - shop_id push routing in HmsMessagingService / MessagingService Demo: - two-shop MultiInstancePane + DemoTheme, lazy shop registration - device-specific legibility fixes (MIUI/Huawei) and stories layout Tests: - instance resolver / registry / push routing / robustness - upgrade migration, did policy, segment A/B
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.