Skip to content

[SDK-675] Restore offline queueing for disableDevice and registerDeviceToken - #1085

Draft
joaodordio wants to merge 2 commits into
masterfrom
feature/SDK-675-offline-disable-device
Draft

[SDK-675] Restore offline queueing for disableDevice and registerDeviceToken#1085
joaodordio wants to merge 2 commits into
masterfrom
feature/SDK-675-offline-disable-device

Conversation

@joaodordio

@joaodordio joaodordio commented Aug 25, 2026

Copy link
Copy Markdown
Member

📝 Summary

Restores offline queueing for users/disableDevice, which regressed in 3.7.0, and adds users/registerDeviceToken alongside it so a logout-then-login replays in the right order.

🎟️ Jira Ticket: SDK-675

📖 Description

users/disableDevice was offline-queueable until 3.7.0, when it was removed from offlineApiSet for cross-SDK list parity. iOS later added queueing for it, Android never restored it. That left Android as the only SDK that silently drops a device disable made while the network is unavailable.

This PR does four things.

Re-adds users/disableDevice to offlineApiSet. A disable made offline is persisted and retried instead of being dropped.

Adds users/registerDeviceToken alongside it, deliberately. The offline queue drains in scheduledAt order, so a logout-then-login sequence replays as disable-then-register and leaves the device enabled. Queueing the disable on its own would be worse than not queueing it at all: a stale disable could land after the new user had already registered and silently kill a live push registration, because the backend merge is last-write-wins. There is a guard test asserting the exact set membership in both directions so neither endpoint can be dropped without the test failing.

One behaviour change worth calling out for reviewers: the offline queue only drains while the app is in the foreground, so a Firebase token refresh received in the background is now sent on the next foreground rather than immediately.

Preserves a queued disable across logout. IterableTaskStorage.deleteAllTasksExcept(name) purges the queue but keeps tasks matching a resource path, and onLogout now uses it to keep users/disableDevice. Without this, switching users would purge the disable before it could reach the user it was created for. The predicate is name IS NULL OR name != ? so it handles the nullable name column correctly, and it runs in a transaction so a task created between the query and the delete still gets settled.

Settles the handlers of discarded tasks. A queued request that is thrown away now calls its failure handler instead of never calling back. This matters most for the completion handlers passed to setEmail and setUserId: they travel with the queued users/registerDeviceToken, so logging in as a different user used to strand them, and an app that dismisses a login spinner in that callback would wait forever. The failure reason states that the request was discarded because the user logged out.

The reset is identity-guarded. It clears only the handler pair the registration was created with, because a registration outcome can arrive after the next setEmail has installed its own handlers. Clearing unconditionally would drop the incoming login's handlers and its callback would never fire.

Checkstyle. IterableApi.java sat at 1999 lines against the default 2000 line cap, so any change that has to touch it breaks the build. Suppressed FileLength for that one file rather than raising the global limit, which would make the rule meaningless everywhere else. The file needs a real split and that is tracked separately in SDK-677; the suppression should be deleted with it.

🧪 How to test?

Unit tests cover all of it: 695 tests, 0 failures.

  • OfflineDisableDeviceQueueTest drives the real disablePush() path end to end: a disable made offline is queued, survives a logout, and is ordered before a subsequent register so the FIFO drain leaves the device enabled.
  • OfflinePurgedRequestCallbackTest covers the handler settling: a purged register fires its failure handler, a preserved disableDevice keeps its handlers, deleteAllTasks settles everything, and the identity-guarded reset does not clobber a newer login's handlers.
  • OfflineRequestProcessorTest has the exact offlineApiSet membership guard and the logout-preservation case.

Manual check, if you want one: enable offline mode, put the device in airplane mode, call disablePush(), then call setEmail with a different user. The disable should still be in the queue and should send on reconnect.

🧾 Changelog

Added to CHANGELOG.md under Unreleased / Fixed: three entries covering the restored disablePush() offline support, the registerDeviceToken queueing with the foreground-drain note, and the discarded-request callback fix.

📹 Loom recording if applicable

Not recorded.

🐞 Github Issues solved

None known.

📚 Docs PR if applicable

No public API change, so no docs PR. The behaviour change is that offline disablePush() and registerDeviceToken are now retried rather than dropped, which restores documented 3.5.16 behaviour rather than introducing new behaviour.


Note for reviewers: SDK-673, runtime project switching, is stacked on top of this branch and depends on it. Its "queued device disables survive the switch" behaviour is inert without the offlineApiSet change here.

…ceToken

users/disableDevice was offline-queueable until 3.7.0, when it was removed from
offlineApiSet for cross-SDK list parity. iOS later added queueing for it, Android
never restored it, which left Android as the only SDK that silently drops a device
disable made while the network is down.

Adds both users/disableDevice and users/registerDeviceToken back to offlineApiSet.
Register is queued alongside disable deliberately: the queue drains in scheduledAt
order, so a logout-then-login sequence replays as disable-then-register and leaves
the device enabled. Queueing the disable on its own would let a stale disable land
after the new user registered and kill a live registration, because the backend
merge is last-write-wins.

A queued disable is also preserved across logout, via deleteAllTasksExcept, so it
still reaches the user it was created for rather than being purged with the rest of
the queue.

Discarded tasks now settle their handlers instead of never calling back. The
setEmail/setUserId completion handlers travel with the queued registerDeviceToken,
so logging in as a different user used to strand them and an app dismissing a
spinner in that callback would wait forever. The reset is identity-guarded so it
only clears the handler pair the registration was created with, otherwise it would
drop the incoming login's handlers.

checkstyle: IterableApi.java sat at 1999 lines against the default 2000 cap, so any
change touching it breaks the build. Suppressed for that one file rather than
raising the global limit. The real split is tracked in SDK-677.
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