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
lastActiveAton 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:
touchstays 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:
_isFlushingPayloadflag 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
_PendingDevicePayloadfor 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