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).
Integration (Recommended: Race-Free Initialization)
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:
timezoneOffsetMinutesis 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:
- DeviceInfo for the device document model
- AuthServiceInt for authentication lifecycle callbacks
- Implementers
Constructors
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