Der Firestore-Write warf zur Laufzeit eine Exception (nicht serialisierbares Objekt), wodurch auch der FCM-Token nie gespeichert wurde. Fehler bei der Push-Registrierung werden jetzt geloggt statt verschluckt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
3.3 KiB
Dart
91 lines
3.3 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
||
import 'package:firebase_auth/firebase_auth.dart';
|
||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:flutter_timezone/flutter_timezone.dart';
|
||
|
||
import '../../../core/firebase/firebase_providers.dart';
|
||
|
||
/// Registriert das Gerät für Push-Erinnerungen und hält die
|
||
/// Erinnerungs-Einstellungen im Nutzer-Dokument aktuell.
|
||
///
|
||
/// Abstrakte Basis, damit Tests eine No-Op-Variante einsetzen können.
|
||
abstract class PushRegistrationService {
|
||
Future<void> registerDevice(User user);
|
||
Future<void> syncReminderTime(TimeOfDay time);
|
||
}
|
||
|
||
class FirebasePushRegistrationService implements PushRegistrationService {
|
||
FirebasePushRegistrationService(this._firestore, this._auth);
|
||
|
||
final FirebaseFirestore _firestore;
|
||
final FirebaseAuth _auth;
|
||
bool _tokenListenerAttached = false;
|
||
|
||
@override
|
||
Future<void> registerDevice(User user) async {
|
||
final messaging = FirebaseMessaging.instance;
|
||
|
||
// Fragt beim ersten Mal die Push-Berechtigung ab (iOS-Dialog,
|
||
// Android 13+ Systemdialog); danach ist der Aufruf ein No-Op.
|
||
final settings = await messaging.requestPermission();
|
||
if (settings.authorizationStatus == AuthorizationStatus.denied) return;
|
||
|
||
final token = await messaging.getToken();
|
||
if (token != null) {
|
||
await _saveToken(user.uid, token);
|
||
}
|
||
if (!_tokenListenerAttached) {
|
||
_tokenListenerAttached = true;
|
||
messaging.onTokenRefresh.listen((newToken) {
|
||
final current = _auth.currentUser;
|
||
if (current != null) _saveToken(current.uid, newToken);
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> _saveToken(String uid, String token) async {
|
||
final timezone = await FlutterTimezone.getLocalTimezone();
|
||
await _firestore.collection('users').doc(uid).set({
|
||
'fcmTokens': FieldValue.arrayUnion([token]),
|
||
// IANA-Kennung, z. B. "Europe/Berlin" – das Objekt selbst ist nicht
|
||
// Firestore-serialisierbar.
|
||
'timezone': timezone.identifier,
|
||
}, SetOptions(merge: true));
|
||
}
|
||
|
||
@override
|
||
Future<void> syncReminderTime(TimeOfDay time) async {
|
||
final user = _auth.currentUser;
|
||
if (user == null) return;
|
||
final formatted =
|
||
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
||
await _firestore.collection('users').doc(user.uid).set({
|
||
'reminderTime': formatted,
|
||
}, SetOptions(merge: true));
|
||
}
|
||
}
|
||
|
||
final pushRegistrationServiceProvider = Provider<PushRegistrationService>((ref) {
|
||
return FirebasePushRegistrationService(
|
||
ref.watch(firestoreProvider),
|
||
ref.watch(firebaseAuthProvider),
|
||
);
|
||
});
|
||
|
||
/// Sorgt dafür, dass das Gerät registriert ist, sobald (und solange) ein
|
||
/// Nutzer angemeldet ist — auch wenn der Login schon vor dem App-Start
|
||
/// bestand. Wird in app.dart per ref.watch aktiv gehalten.
|
||
final pushSetupProvider = Provider<void>((ref) {
|
||
final user = ref.watch(authStateProvider).value;
|
||
if (user != null) {
|
||
ref.read(pushRegistrationServiceProvider).registerDevice(user).catchError(
|
||
(Object error, StackTrace stack) {
|
||
// Bewusst nur loggen: Push-Registrierung darf den App-Start nie
|
||
// blockieren, aber Fehler müssen im Log sichtbar sein.
|
||
debugPrint('Push-Registrierung fehlgeschlagen: $error');
|
||
},
|
||
);
|
||
}
|
||
});
|