DeviceServiceInt class abstract

Service interface for managing device registration and timezone tracking.

The DeviceService tracks device-level information with timezone as the primary use case. It maintains a canonical Firestore document per install/profile at users/{uid}/devices/{deviceId} containing:

  • Device state for timezone-aware logic (timezone, offset, lastActiveAt)
  • Current push token when available (for notification delivery)

Separation of Concerns

  • DeviceService owns: device identity, timezone/offset tracking, activity tracking (lastActiveAt), backend persistence (all Firestore device doc writes), and deleting the device doc on logout.
  • NotificationService owns: permission prompting, token acquisition, and local token lifecycle. It forwards token changes to DeviceService for persistence but does NOT persist tokens during logout (DeviceService handles cleanup by deleting the device doc).

Use DreamicServices.initialize for the simplest, race-free setup:

// Single call handles all wiring correctly
final services = await DreamicServices.initialize(
  firebaseApp: Firebase.app(),
  enableDeviceService: true,
  enableNotifications: true,
);

For manual wiring with race-free initialization, pass callbacks to AuthService constructor and use initialize:

// 1. Create services
final deviceService = DeviceServiceImpl();
final notificationService = NotificationService();

// 2. Register in GetIt early
GetIt.I.registerSingleton<DeviceServiceInt>(deviceService);

// 3. Create auth with callbacks pre-registered (race-free)
final auth = AuthServiceImpl(
  firebaseApp: app,
  onAuthenticatedCallbacks: [deviceService.handleAuthenticated],
  onAboutToLogOutCallbacks: [deviceService.handleAboutToLogOut],
);

// 4. Initialize services in parallel (critical for race-free!)
await Future.wait([
  deviceService.initialize(authService: auth),
  notificationService.initialize(authService: auth),
]);

Legacy Integration

The connectToAuthService method is still available for apps that:

  • Don't have warm-start race issues (e.g., always require fresh login)
  • Want to connect services dynamically after auth is established
// Legacy approach (may miss auth events on warm start)
await deviceService.connectToAuthService();

See docs/plans/auth-race/plan.auth-race.md for migration details.

Best-Effort Operations

All device operations are best-effort and should never block the app. Failures are logged but not surfaced to users. A pending payload system ensures eventual consistency across flaky networks.

Key Constraints

  • DST-safe: timezoneOffsetMinutes is refreshed even when the IANA timezone string doesn't change (to handle DST transitions).
  • Efficient: Throttling prevents unnecessary writes on frequent app resume events.
  • Robust: Offline failures don't block the app; sync retries on later lifecycle events.

See also:

Implementers

Constructors

DeviceServiceInt()

Properties

hashCode int
The hash code for this object.
no setterinherited
isConnectedToAuth bool
Whether this service is connected to auth.
no setter
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.
getCurrentTimezone() Future<String>
Gets the current device's IANA timezone string.
getDeviceId() Future<String>
Gets the current device's unique identifier.
getMyDevices() Future<Either<RepositoryFailure, List<DeviceInfo>>>
Gets all devices registered for the current user.
handleAboutToLogOut() Future<void>
Public handler for pre-logout cleanup.
handleAuthenticated(String? uid) Future<void>
Public handler for auth state changes. Called when user authenticates.
initialize({required AuthServiceInt authService}) Future<void>
Initializes DeviceService with auth reference for race-free setup.
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.
registerDevice() Future<Either<RepositoryFailure, Unit>>
Registers or updates the current device in Firestore.
toString() String
A string representation of this object.
inherited
touchDevice() Future<Either<RepositoryFailure, Unit>>
Marks this device as active by updating lastActiveAt.
unregisterDevice() Future<Either<RepositoryFailure, Unit>>
Removes the current device registration from Firestore.
updateTimezoneOrOffsetIfChanged() Future<Either<RepositoryFailure, bool>>
Updates timezone and/or offset in Firestore if changed.

Operators

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