DeviceServiceImpl class

Implementation of DeviceServiceInt for device registration and timezone tracking.

This implementation provides production-ready device tracking with:

  • Device Identity: Generates and persists a UUIDv4 per app install
  • Timezone Tracking: Syncs IANA timezone and offset with DST awareness
  • Activity Tracking: Updates lastActiveAt on app resume (throttled)
  • Offline Resilience: Pending payload system for eventual consistency
  • Lifecycle Integration: Automatic auth and app lifecycle wiring

Quick Start

// 1. Create and register the service
final deviceService = DeviceServiceImpl();
GetIt.instance.registerSingleton<DeviceServiceInt>(deviceService);

// 2. Connect to auth service (enables automatic lifecycle management)
await deviceService.connectToAuthService();

// That's it! Device registration, timezone updates, and activity
// tracking are now automatic based on auth and app lifecycle events.

Integration with NotificationService

For apps using push notifications, NotificationService auto-detects DeviceService in GetIt and forwards token changes:

await deviceService.connectToAuthService();
await notificationService.connectToAuthService(); // Auto-forwards tokens

Internal State Management

The service maintains several pieces of in-memory state:

  • _deviceId: Cached device ID (loaded from SharedPreferences)
  • _cachedTimezone: Last synced timezone (for change detection)
  • _cachedOffsetMinutes: Last synced offset (for DST detection)
  • _lastServerSyncAt: Timestamp of last successful timezone sync
  • _lastTouchAt: Timestamp of last successful touch operation
  • _pendingPayload: Pending updates awaiting sync (offline resilience)

Offline Handling

When backend calls fail (network errors, server issues), updates are stored in a _PendingDevicePayload that persists to SharedPreferences:

  • Merge semantics: Per-field last-write-wins
  • Sticky flags: touch stays true until successful flush
  • Backoff: 15 minutes between retry attempts (bypassed for changes)
  • Auto-flush: Pending data is flushed on auth events and app resume

Throttling Configuration

All throttle values can be configured via AppConfigBase or Remote Config:

  • Timezone unchanged: 48 hours (avoids resume spam)
  • Timezone changed: 10 min debounce (prevents flapping)
  • Touch throttle: 60 minutes (limits activity updates)
  • Pending backoff: 15 minutes (retry interval)

Thread Safety

The service is designed for single-isolate use (main isolate). Concurrent calls are handled via:

  • _isFlushingPayload flag prevents concurrent pending payload flushes
  • Callback idempotency in connectToAuthService()

Backend Requirements

This implementation requires the Firebase Functions scaffolding deployed to your project. The callable function name is configured via AppConfigBase.deviceActionFunction (default: "deviceAction").

See scaffolding/firebase_functions/device/README.md for setup.

See Also

  • DeviceServiceInt for the interface contract and method documentation
  • DeviceInfo for the device document model
  • _PendingDevicePayload for offline handling internals
Implemented types

Constructors

DeviceServiceImpl()
Creates a new DeviceServiceImpl instance.

Properties

hashCode int
The hash code for this object.
no setterinherited
isConnectedToAuth bool
Whether this service is connected to auth.
no setteroverride
runtimeType Type
A representation of the runtime type of the object.
no setterinherited

Methods

connectToAuthService({AuthServiceInt? authService, Future<void> onAuthenticated(String? uid)?, Future<void> onAboutToLogOut()?}) Future<void>
Connects this DeviceService to AuthService for automatic lifecycle wiring.
override
getCurrentTimezone() Future<String>
Gets the current device's IANA timezone string.
override
getDeviceId() Future<String>
Gets the current device's unique identifier.
override
getMyDevices() Future<Either<RepositoryFailure, List<DeviceInfo>>>
Gets all devices registered for the current user.
override
handleAboutToLogOut() Future<void>
Public handler for pre-logout cleanup.
override
handleAuthenticated(String? uid) Future<void>
Public handler for auth state changes. Called when user authenticates.
override
initialize({required AuthServiceInt authService}) Future<void>
Initializes DeviceService with auth reference for race-free setup.
override
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
persistFcmToken({required String? fcmToken}) Future<Either<RepositoryFailure, Unit>>
Persists the FCM token to the backend device record.
override
registerDevice() Future<Either<RepositoryFailure, Unit>>
Registers or updates the current device in Firestore.
override
toString() String
A string representation of this object.
inherited
touchDevice() Future<Either<RepositoryFailure, Unit>>
Marks this device as active by updating lastActiveAt.
override
unregisterDevice() Future<Either<RepositoryFailure, Unit>>
Removes the current device registration from Firestore.
override
updateTimezoneOrOffsetIfChanged() Future<Either<RepositoryFailure, bool>>
Updates timezone and/or offset in Firestore if changed.
override

Operators

operator ==(Object other) bool
The equality operator.
inherited