Skip to content

Dev 4073 huawei push integration - #208

Merged
Xyzcancer merged 21 commits into
masterfrom
DEV-4073-huawei-push-integration
Jul 22, 2026
Merged

Dev 4073 huawei push integration#208
Xyzcancer merged 21 commits into
masterfrom
DEV-4073-huawei-push-integration

Conversation

@Xyzcancer

Copy link
Copy Markdown
Contributor

No description provided.

Xyzcancer added 21 commits May 13, 2026 18:00
…/FCM token sources, http logger, demo updates)
Bring the Huawei push WIP up to date with master (catalog/loyalty/orders
methods + the FCM push-routing/robustness fix + BigPicture demo display).
Conflict resolution:
- SDK.kt: keep huawei's multi-instance push routing (activeInstances) AND
master's currentInstance/isSdkInitialized so SDK.instance returns the
initialized SDK and notificationReceived stays guarded.
- DemoApplication: Application-side SDK init + BigPicture display listener
(master) plus the HTTP-log hook (huawei).
- MainActivity: reuse SDK.instance; show the registered push provider + token
via the unified PushProvider API (FCM + HMS), seeded from cache and Firebase
so it appears immediately; keep POST_NOTIFICATIONS + notification-click.
- activity_main.xml: combined header (provider + token + Copy + HTTP Log)
over the full demo button list.
- build.gradle / strings.xml: drop duplicate google-services apply and the
duplicate push_token_copied string left by the merge.
HMS was forced on every consumer as `implementation`, so a plain FCM app had to
add the Huawei Maven repo just to resolve the transitive deps, and the demo would
not build without agconnect-services.json.
Make Huawei opt-in:
- HMS deps (hms:push, agconnect-core) -> compileOnly: neither bundled nor listed
in the published POM, so FCM-only apps build with no Huawei footprint.
- PushTokenManager registers a provider only if its SDK is on the classpath
(Class.forName marker check), so HmsTokenSource is never referenced without HMS
present -> no NoClassDefFoundError.
- Ship consumer-rules.pro (-dontwarn com.huawei.**) so a FCM-only consumer's R8
does not fail on the absent HmsMessageService superclass.
- demo-app applies the com.huawei.agconnect plugin only when agconnect-services.json
exists (mirrors the existing google-services guard), so the demo builds without it.
To enable Huawei push a host app adds the HMS artifacts + the agconnect plugin +
agconnect-services.json (documented at the compileOnly declaration).
… present
Mirror the SDK's optional-HMS model in the demo consumer. Now that the SDK
exposes HMS as compileOnly, the demo (itself a consumer) must add the HMS
artifacts, and only when Huawei is configured. Gate them on the same
agconnect-services.json presence check that applies the agconnect plugin, so a
build without the Huawei config ships a Firebase-only APK with no HMS classes.
HmsMessagingService.onMessageReceived only logged the payload, so data-only HMS
pushes (attach_notification=false) were dropped and nothing was shown.
Notification messages (attach_notification=true) worked only because HMS Core
displays those itself — which is why local test sends appeared but the backend's
data sends did not.
Route HMS the same way as FCM: extract the message's data map and hand it to the
shared SDK entry point, which tracks "received" and forwards it to the
OnMessageListener (the rees46 flavor renders it via NotificationHelper).
- Add a provider-agnostic Map<String, String>.toNotificationData() mapper; the FCM
RemoteMessage extension now delegates to it.
- SDK.receiveMessage / onMessage take the data map; add an onMessage(Map) overload
for HMS so no Huawei types leak into SDK.kt.
- HmsMessagingService forwards message.dataOfMap to SDK.onMessage, guarded so a
handler failure never crashes the HMS thread.
Add NotificationDataMapperTest for Map<String, String>.toNotificationData() — the
single point both the FCM and HMS messaging services route their data payload
through. Covers a full payload (actions/action_urls/event parsed), the data-only
title/body case (HMS), an empty payload (null scalars, empty collections, empty
event), malformed JSON degrading to empty instead of crashing, and the FCM
RemoteMessage.toNotificationData delegating to the shared map mapper.
…ustom event
- Guard FirebaseMessaging.getInstance() behind a FirebaseApp presence check so a
Huawei-only build (no google-services.json) no longer crashes on MainActivity start.
- Rename the demo custom event to the registered "flutter_example" so push/custom
returns 200 instead of 400 "Event custom_event not found".
Notification-mode pushes (attach_notification=true) that FCM/HMS auto-display in the
background now produce a heads-up notification with sound instead of landing silently
in the shade. Same channel id as before; mirrors the React Native SDK reference.
…K logo
- SDK: resolve the notification small icon from the host's default_notification_icon
meta-data, falling back to the host application icon; stop hardcoding the SDK logo.
- Demo: ship its own notification glyph and launcher icon and declare
default_notification_icon/color, replacing the @android:drawable/ic_dialog_info
placeholder that rendered as an "i in a circle" in the status bar.
StoriesView is a View that expects its code through the app:code attribute
and then has to be handed to SDK.initializeStoriesView, which makes it
awkward to use from Compose.
Add StoriesWidget, a composable that does both through AndroidView: it
builds the view with the programmatic constructor, registers it with the
SDK, forwards the OnClickListener whose Boolean return keeps the SDK from
opening a url the host routes itself, and releases the player on dispose.
Rebuild rather than update the view when the code changes, since the block
is loaded once during initialize.
Take Compose as compileOnly, mirroring the optional Huawei dependency: it
stays off every consumer's classpath and out of the published POM, so a
View-based app pulls none of it. compileOnly artifacts take no part in
manifest merging either, so the SDK keeps minSdk 19 even though Compose
itself requires 21.
The stories block sat on the same screen as the SDK method demos, so there
was nowhere to show the new Compose wrapper next to the view it wraps.
Swap three panes from a bottom navigation bar: "API" keeps the method demos
along with the push token panel, which belongs to them rather than being a
header shared by every tab; "UI Kit" renders the block with StoriesWidget;
and "Legacy UI" with the XML StoriesView plus an OnClickListener. Both UI
tabs log their callbacks, and the Compose one can route demo:// links
itself, so the opt-out can be exercised from the demo.
Drive the initial pane through the navigation listener so the checked item
and the visible pane cannot drift apart. The Material component gets a
Bridge theme and explicit item colours, since the app itself stays on an
AppCompat theme whose colorPrimary leaves the checked tab unreadable.
Raise the demo to minSdk 21 for Compose, and opt the layout into
fitsSystemWindows so the bar clears the gesture area on Android 15.
Also carries the theme groundwork that was still uncommitted: the light-in
-every-mode theme with its night override, and the token panel colour.
The previous block was paused and answered with an empty stories array, so
both UI tabs could only render an empty row.
Compose is a compileOnly dependency, and such dependencies are absent from
the unit test classpath. The Compose compiler plugin refuses to compile the
test variant without a runtime it can see, so every unit test in the module
stopped compiling once StoriesWidget was added:
IncompatibleComposeRuntimeVersionException: The Compose Compiler requires
the Compose Runtime to be on the class path, but none could be found.
Mirror the compileOnly declarations as testImplementation. This is confined
to the test configuration, so the published POM still carries no Compose.
On a fresh install the same token reaches onTokenReceived twice: from the
proactive fetch in initialize() and from the messaging service's onNewToken,
which fires because the token is created for the first time. shouldSendToken
compares against the persisted token, but the token is only persisted in the
response callback of the very request the check is meant to prevent, so both
deliveries saw an empty cache and both posted mobile_push_tokens.
Testers saw two requests on Xiaomi and four on Huawei, where FCM and HMS are
both available and each doubles the same way.
Claim the provider:token pair in a concurrent set before the request leaves
and release it once the token is persisted, so a delivery arriving in between
is deduplicated by the cache instead. Failures release the claim immediately
so a later delivery can retry. The key includes the provider, so an FCM and
an HMS registration never suppress each other.
The notification the SDK builds itself carried neither a priority nor any
default alert. From Android 8 the channel decides and NOTIFICATION_CHANNEL is
IMPORTANCE_HIGH, so nothing changes there. Below that there are no channels: a
heads-up pop-up needs both a high priority and a sound or vibration, and
without them the push lands silently in the shade. minSdk is 19, so those
devices are still in scope.
Both other SDKs that build notifications on Android already do this — the
Flutter notifier sets PRIORITY_HIGH with DEFAULT_ALL, and the React Native one
sets AndroidImportance.HIGH per notification. This aligns Android with them.
GET /stories/{code} nests both captions of the product carousel toggle inside
a "labels" object:
"labels": { "hide_carousel": "Скрыть товары", "show_carousel": "Товары для геймеров" }
The parser located that object and then read the fields off the parent element
instead, so both captions were always empty and the button rendered blank. The
button itself still worked, which is why this went unnoticed.
StoriesManager keeps a single StoriesView reference, so of the two panes only
the one that registered last receives loaded stories — the Compose pane, which
left the Legacy tab permanently empty. The button re-registers the pane's own
view (the XML one directly, the Compose one by rebuilding the widget), which
pulls the block back into it.
A workaround for the demo, not a fix: the SDK still drives one block at a time.
Remove once it can hold more than one.
… story
OnClickListener.onCloseDialogClick defaulted to true. In ProductsAdapter a true
return closes the story viewer, while false opens the product url — so a host
that did not override this callback (the common case) saw a product tap close
the story and open nothing. Testers reported it as "the story just closes on
tap".
Flip the default to false so an untouched listener opens the product, which is
what a tap on a product card should do. Hosts that want the old behaviour still
get it by returning true explicitly. The KDoc described the inverse of what the
code does; corrected it to match.
Groundwork for multi-instance (Release 1), internal only — no public API
change and no behaviour change. The static routing state that lived in SDK's
companion (currentInstance + activeInstances) moves into an internal
SdkRegistry that owns the ordered fan-out set, the current-default pointer and
a shop_id -> instance mapping.
initialize() now calls SdkRegistry.register(shopId, this); release() calls
unregister(this); SDK.instance and the push entry points (onMessage,
onPushTokenReceived) read through the registry. Single-instance behaviour is
unchanged; the shop_id resolution the public Rees46.initialize/getInstance API
will build on is in place and covered by tests. Not wired into the demo yet.
@Xyzcancer
Xyzcancer merged commit 8c81f41 into masterJul 22, 2026
1 check passed
@Xyzcancer
Xyzcancer deleted the DEV-4073-huawei-push-integration branch July 22, 2026 13:37
Sign up for freeto 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

@Xyzcancer