registerDevice method

  1. @override
Future<Either<RepositoryFailure, Unit>> registerDevice()
override

Registers or updates the current device in Firestore.

Called automatically on login/auth refresh when connected to AuthService. Creates or updates the device document with current timezone, offset, platform, app version, and marks the device as active.

This is best-effort and should not block app startup. On success, updates internal cache for timezone/offset throttling.

When Called

  • On initial login (via addOnAuthenticatedCallback)
  • Can be called manually if needed

Returns

  • Right(unit) on success
  • Left(RepositoryFailure) on failure (logged, not surfaced to user)

Example

final result = await deviceService.registerDevice();
result.fold(
  (failure) => log('Device registration failed: $failure'),
  (_) => log('Device registered successfully'),
);

Implementation

@override
Future<Either<RepositoryFailure, Unit>> registerDevice() async {
  logd('DeviceService: registerDevice called');

  try {
    final deviceId = await getDeviceId();
    final timezone = await getCurrentTimezone();
    final offsetMinutes = _getCurrentOffsetMinutes();
    final platform = _getCurrentPlatform();
    final platformString = DevicePlatformSerialization.serialize(platform);
    final packageInfo = await PackageInfo.fromPlatform();

    // Check authentication - if not authenticated, store pending and return
    if (!_isUserAuthenticated()) {
      logd('DeviceService: User not authenticated, storing pending registration');
      await _updatePendingPayload(
        deviceId: deviceId,
        timezone: timezone,
        timezoneOffsetMinutes: offsetMinutes,
        platform: platformString,
        appVersion: packageInfo.version,
        touch: true,
        hasChangedFields: true,
      );
      // Return success since we've stored it for later - this is best-effort
      return const Right(unit);
    }

    logd('DeviceService: Registering device $deviceId with timezone $timezone');

    // First, try to flush any existing pending payload
    // This ensures we don't lose pending changes when registering
    await _flushPendingPayload();

    final result = await _deviceCallable.call({
      'action': 'register',
      'deviceId': deviceId,
      'timezone': timezone,
      'timezoneOffsetMinutes': offsetMinutes,
      'platform': platformString,
      'appVersion': packageInfo.version,
    });

    // Check for success
    final data = Map<String, dynamic>.from(result.data as Map);
    if (data['success'] != true) {
      logw('DeviceService: Registration response indicated failure');
      // Store in pending payload for retry
      await _updatePendingPayload(
        deviceId: deviceId,
        timezone: timezone,
        timezoneOffsetMinutes: offsetMinutes,
        platform: platformString,
        appVersion: packageInfo.version,
        touch: true,
        hasChangedFields: true,
      );
      return const Left(RepositoryFailure.unexpected);
    }

    // Update cached values on success
    _cachedTimezone = timezone;
    _cachedOffsetMinutes = offsetMinutes;
    _lastServerSyncAt = DateTime.now();
    _lastTouchAt = DateTime.now(); // registerDevice also updates lastActiveAt

    // Clear any pending payload since we just synced
    await _clearPendingPayload();

    // Log if timezone changed
    if (data['timezoneChanged'] == true) {
      logd('DeviceService: Timezone changed from ${data['previousTimezone']} to $timezone');
    }

    logd('DeviceService: Device registered successfully');
    return const Right(unit);
  } on FirebaseFunctionsException catch (e) {
    loge(e, 'DeviceService: Firebase Functions error during registration');

    // Store in pending payload for retry on network/transient errors
    if (_shouldStorePendingOnError(e)) {
      await _storePendingRegistration();
    }

    return _mapFirebaseFunctionsException(e);
  } catch (e) {
    loge(e, 'DeviceService: Unexpected error during registration');

    // Store in pending payload for retry
    await _storePendingRegistration();

    return const Left(RepositoryFailure.unexpected);
  }
}