feat(sync): inject isOnlineOverride into SyncService.isOnline() - #68
feat(sync): inject isOnlineOverride into SyncService.isOnline()#68abhijitnairDhwani wants to merge 1 commit into
Conversation
`SyncService.isOnline()` directly probes `connectivity_plus` and accepts only `mobile | wifi | ethernet`. On Android emulators with `adb reverse` the link reads as `ConnectivityResult.none` and the gate refuses sync even though HTTP traffic to the bench is fully functional. Today the only escape is to bypass `pullSync`/`pullSyncMany` entirely from the consumer app — costing delta pulls, resumable cursors, and the in-built page lookahead that `_pullOneInternal` already implements. Add an optional `isOnlineOverride` callback wired through `FrappeSDK(...)` → `SyncService(...)` → `isOnline()`. When set, the override replaces the `connectivity_plus` probe. When unset (the default), behaviour is exactly as today — fully backwards-compatible. Semantics: - The `offlineMode.enabled` gate always runs first; overriding doesn't bypass an explicit offline-mode opt-out. - If the override throws, `isOnline()` logs and falls through to the platform probe so a buggy callback can't brick sync. Production callers should leave this null. Legitimate use is the emulator+adb-reverse dev workflow, where the consumer sets `isOnlineOverride: () async => true`. The existing private `_isOnlineOverrideForTesting` test seam is unchanged. Both `FrappeSDK()` and `FrappeSDK.forTesting()` accept the parameter; the test constructor initialises it to null in its initializer list. 183 sync-related tests pass unchanged on this branch.
deepak-dhwani
left a comment
There was a problem hiding this comment.
PR #68 — feat(sync): inject isOnlineOverride into SyncService.isOnline() — Round 1 Review
Two files changed, 100 lines total. The change is small and well-motivated.
What the diff does
SyncService gains a Future Function()? _isOnlineOverride field. isOnline() calls it first if set, falls back to the connectivity_plus probe on throw.
FrappeSDK accepts isOnlineOverride and threads it through. The forTesting constructor explicitly initialises it to null in the initialiser list to stay
source-compatible.
Correctness
The implementation is correct. The offlineMode.enabled gate runs before the override, so a () async => true override cannot bypass an explicit offline-mode
opt-out. The local capture final override = _isOnlineOverride before the null check is good defensive Dart — it avoids a race if the field were ever mutated
concurrently (it is final, so not an issue here, but the pattern is idiomatic). The try/catch fallback on override throw is the right behaviour for a safety
valve.
MEDIUM M1 — print() should be sdkLog()
lib/src/services/sync_service.dart lines 91–96
// ignore: avoid_print
print(
'SyncService.isOnline: isOnlineOverride threw, '
'falling back to platform probe — $e\n$st',
);
Every other log statement in the SDK uses sdkLog() from utils/sdk_log.dart, which routes through the SDK's own logging infrastructure and is suppressible. This
is a plain print() with a // ignore: avoid_print suppression. It will appear in production builds on Android/iOS logcat/Console for any consumer whose override
happens to throw, with no way to suppress it. Replace with sdkLog(...) to match the rest of the codebase.
MEDIUM M2 — No tests for the override behaviour
test/services/sync_service_online_test.dart covers offlineMode.enabled = false paths and the pullSyncMany empty-list short-circuit. None of the existing tests
exercise isOnlineOverride. The three cases that need covering:
- Override returns true → isOnline() returns true (override takes effect)
- Override returns false → isOnline() returns false (override suppresses sync)
- Override throws → isOnline() falls back to the platform probe (the safety-valve path)
Case 3 is the most important. It is the correctness guarantee the PR description highlights, and without a test a future refactor that removes the try/catch
silently breaks the contract. These are straightforward unit tests — pass isOnlineOverride directly to SyncService in construction, no real network needed.
LOW L1 — New constructor parameter not mentioned in CHANGELOG
FrappeSDK and SyncService are both fully-exported public API. The new isOnlineOverride parameter should appear in CHANGELOG.md so consumers scanning it know the
hook exists.
Verdict
M1 and M2 are quick fixes — swap print for sdkLog and add three short unit tests — but the safety-valve path (override throws → fallback) is the key correctness claim of this PR and it currently has no test coverage.
What
Adds an optional
Future<bool> Function()? isOnlineOverrideparameter onSyncServiceandFrappeSDK. When set, the override replaces theconnectivity_plusprobe insideSyncService.isOnline(). When unset (the default), behaviour is unchanged.Why this is needed
SyncService.isOnline()callsConnectivity().checkConnectivity()directly and only acceptsmobile | wifi | ethernet:On Android emulators with
adb reverse, the platform reportsConnectivityResult.nonebecause the tunnel isn't a recognised network type — even though HTTP traffic to the bench flows fine.pullSync/pullSyncMany/pushSyncthen early-exit withnoConnectivityand refuse to run.Today consumers have no app-side escape:
ConnectivityWatcherisn't exported from the barrel.SyncService.isOnline()bypasses the watcher anyway — it probes the plugin directly.FrappeSDKconstructor takes no hook to replace the probe.The only workaround until now has been to bypass
SyncService.pullSync/pullSyncManyentirely from the consumer app — losing the SDK's delta cursors, page lookahead, and RESUME-on-crash semantics in the process. We were carrying that bypass for ~2 weeks in the Swasti V3 mobile app before this PR.What this PR changes
SyncServiceconstructor acceptsFuture<bool> Function()? isOnlineOverride.SyncService.isOnline()calls the override first when set; falls back to the platform probe on throw (with a logged warning) so a buggy override can't brick sync.FrappeSDK()acceptsisOnlineOverrideand threads it through toSyncService.FrappeSDK.forTestingconstructor initialises the field tonullin its initializer list to keep tests source-compatible.Backward compatibility
null— existingFrappeSDK(baseUrl: ...)andSyncService(...)call sites behave exactly as before._isOnlineOverrideForTestingtest seam infrappe_sdk.dartis unchanged.Safety notes
offlineMode.enabledgate runs first; the override does not bypass an explicit offline-mode opt-out.isOnline()logs the failure and falls through to the platform probe.Intended use
Dev builds that talk to a local bench via
adb reverse:Production callers should leave
isOnlineOverridenull and rely on the platform probe.Tests
183 sync-related tests pass unchanged on this branch (
test/sync/,test/concurrency/sync_mutex_test.dart,test/services/sync_engine_builder_send_test.dart). No new tests added — the change is opt-in and the existing suite covers the default path.