Skip to content

Instantly share code, notes, and snippets.

@develop4God
Created December 23, 2025 13:04
Show Gist options
  • Select an option

  • Save develop4God/de82c6ffdcf7ce5f79ff88c705672034 to your computer and use it in GitHub Desktop.

Select an option

Save develop4God/de82c6ffdcf7ce5f79ff88c705672034 to your computer and use it in GitHub Desktop.
Análisis completo de develop4God/Devocional_nuevo (lib, i18n, test, pubspec.yml) (PR: #161)
🔍 ANÁLISIS DE PULL REQUEST #161
============================================================
📋 INFORMACIÓN GENERAL:
• Título: Migrate NotificationService from singleton to ServiceLocator DI
• Estado: open (Open/Closed)
• Autor: Copilot
• Creado: 2025-12-23 05:34:23
• Rama origen: copilot/migrate-notification-service-di
• Rama destino: feature/new-chinese-language-zh
📝 DESCRIPCIÓN:
NotificationService used a singleton pattern inconsistent with the project's DI strategy. Migrated to ServiceLocator registration alongside LocalizationService, TtsService, and AnalyticsService.
## Changes
**NotificationService (`lib/services/notification_service.dart`)**
- Removed singleton pattern: static `_instance`, factory constructor, `_internal()` constructor
- Added DI usage documentation
**ServiceLocator (`lib/services/service_locator.dart`)**
- Registered NotificationService as lazy singleton in `setupServiceLocator()`
**Usage sites (3 locations)**
- `lib/main.dart`: Background handler now calls `setupServiceLocator()` for isolate initialization; foreground uses `getService<NotificationService>()`
- `lib/pages/notification_config_page.dart`: Retrieves service via `getService<NotificationService>()` in `initState()`
**Tests**
- Updated `test/critical_coverage/notification_service_working_test.dart` with ServiceLocator setup/teardown
- Added registration tests in `test/unit/services/service_locator_test.dart`
## Usage
**Before:**
```dart
final notificationService = NotificationService(); // singleton instance
await notificationService.initialize();
```
**After:**
```dart
final notificationService = getService<NotificationService>();
await notificationService.initialize();
```
**Background isolate pattern:**
```dart
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
setupServiceLocator(); // Initialize DI in isolate
final notificationService = getService<NotificationService>();
// ...
}
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>Migrate NotificationService to ServiceLocator DI and remove singleton usage</issue_title>
> <issue_description>## Background
> Currently, `NotificationService` is implemented as a singleton (`NotificationService._instance`), which is considered an antipattern and makes the code harder to test, maintain and extend. Dependency injection via a Service Locator is already used for other services in the project (see `service_locator.dart`).
>
> ## Task
> - Refactor `NotificationService` to remove singleton implementation.
> - Integrate it into the `ServiceLocator` DI setup, similar to other services.
> - Ensure all usages of `NotificationService` now obtain it via DI (ServiceLocator) rather than direct singleton access.
> - Update initialization logic so setup (configure and initialize FCM, permissions, etc.) is handled through ServiceLocator lifecycle, not through static instance methods.
> - Adapt any test/mocking logic to support proper dependency injection.
> - Remove or rewrite convenience factories that rely on static singleton patterns for notifications.
>
> ## Acceptance Criteria
> - No singleton instance of `NotificationService` remains in codebase.
> - All usages of `NotificationService` obtain it via ServiceLocator DI.
> - No disruption to current functionality, all notification features work as before.
> - Documentation updated (usage/comments/examples) to reflect the new DI pattern.
> - Add test with new DI and change and the existing test must change to the new pattern. All test must pass no failing.
> - dart format, analyze fatal Infos and dart fix no errors. Flutter run successfully and integration test modify to new DI pattern run with xvfb-run.
>
>
> ---
> This refactor improves testability, maintainability and future extensibility of notification logic by aligning it with the project's DI strategy. Tester </issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes develop4God/Devocional_nuevo#160
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
📊 ESTADÍSTICAS:
• Commits: 3
• Archivos modificados: 8
• Adiciones: 63 líneas
• Eliminaciones: 13 líneas
• Archivos cambiados: 8
🔄 COMMITS:
1. [44f23151] Initial plan
👤 copilot-swe-agent[bot] - 2025-12-23 05:34
2. [fdebca78] Refactor NotificationService to use DI via ServiceLocator
👤 copilot-swe-agent[bot] - 2025-12-23 05:42
3. [223dd705] Fix background handler to setup ServiceLocator in isolate and simplify test
👤 copilot-swe-agent[bot] - 2025-12-23 05:52
📁 ARCHIVOS MODIFICADOS:
📝 lib/main.dart (+10/-2)
📄 DIFF: 33 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fmain.dart
📝 lib/pages/notification_config_page.dart (+4/-1)
📄 DIFF: 26 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fpages%2Fnotification_config_page.dart
📝 lib/services/notification_service.dart (+12/-6)
📄 DIFF: 32 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Fnotification_service.dart
📝 lib/services/service_locator.dart (+7/-0)
📄 DIFF: 21 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Fservice_locator.dart
📝 lib/services/tts/voice_settings_service.dart (+1/-1)
📄 DIFF: 9 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Ftts%2Fvoice_settings_service.dart
📝 test/critical_coverage/notification_service_working_test.dart (+12/-0)
📄 DIFF: 26 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Fcritical_coverage%2Fnotification_service_working_test.dart
📝 test/integration/chinese_user_journey_test.dart (+0/-3)
📄 DIFF: 14 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Fintegration%2Fchinese_user_journey_test.dart
📝 test/unit/services/service_locator_test.dart (+17/-0)
📄 DIFF: 27 líneas de cambio
🔗 RAW: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Funit%2Fservices%2Fservice_locator_test.dart
DIFFS COMPLETOS - PR #161
SOLO ARCHIVOS DE: lib, i18n, test, pubspec.yml
==================================================
📄 ARCHIVO: lib/main.dart
Estado: modified (+10/-2)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fmain.dart
DIFF:
----------------------------------------
@@ -51,6 +51,14 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
name: 'BackgroundServiceCallback',
);
await Firebase.initializeApp();
+
+ // Setup ServiceLocator for background isolate
+ setupServiceLocator();
+ developer.log(
+ 'BackgroundServiceCallback: ServiceLocator initialized in background isolate.',
+ name: 'BackgroundServiceCallback',
+ );
+
tzdata.initializeTimeZones();
try {
final String currentTimeZone = await FlutterTimezone.getLocalTimezone();
@@ -71,7 +79,7 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
name: 'BackgroundServiceCallback',
);
}
- final NotificationService notificationService = NotificationService();
+ final notificationService = getService<NotificationService>();
await notificationService.initialize();
final String? title = message.notification?.title;
final String? body = message.notification?.body;
@@ -432,7 +440,7 @@ class _AppInitializerState extends State<AppInitializer> {
// Notifications - diferido 2 segundos
Future.delayed(const Duration(seconds: 2), () async {
try {
- await NotificationService().initialize();
+ await getService<NotificationService>().initialize();
developer.log(
'AppInitializer: Servicios de notificación inicializados en background.',
name: 'MainApp',
----------------------------------------
📄 ARCHIVO: lib/pages/notification_config_page.dart
Estado: modified (+4/-1)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fpages%2Fnotification_config_page.dart
DIFF:
----------------------------------------
@@ -7,6 +7,7 @@ import 'package:devocional_nuevo/blocs/theme/theme_bloc.dart';
import 'package:devocional_nuevo/blocs/theme/theme_state.dart';
import 'package:devocional_nuevo/extensions/string_extensions.dart';
import 'package:devocional_nuevo/services/notification_service.dart';
+import 'package:devocional_nuevo/services/service_locator.dart';
import 'package:devocional_nuevo/widgets/app_bar_constants.dart';
// NEW IMPORTS for Firebase
import 'package:firebase_auth/firebase_auth.dart';
@@ -22,7 +23,7 @@ class NotificationConfigPage extends StatefulWidget {
}
class _NotificationConfigPageState extends State<NotificationConfigPage> {
- final NotificationService _notificationService = NotificationService();
+ late final NotificationService _notificationService;
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@@ -38,6 +39,8 @@ class _NotificationConfigPageState extends State<NotificationConfigPage> {
@override
void initState() {
super.initState();
+ // Get NotificationService from ServiceLocator
+ _notificationService = getService<NotificationService>();
_initializeFirebaseAndLoadSettings();
}
----------------------------------------
📄 ARCHIVO: lib/services/notification_service.dart
Estado: modified (+12/-6)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Fnotification_service.dart
DIFF:
----------------------------------------
@@ -3,6 +3,18 @@
//notification_service.dart - Guardar lastLogin en Firestore
//notification_service.dart (Ajuste FCM y Autenticación para que no haya usuario nulo)
//notification_service.dart (Ajuste de Permisos)
+//
+// NotificationService - Migrated to Dependency Injection
+// This service manages Firebase Cloud Messaging (FCM), local notifications,
+// and notification settings. It is registered in ServiceLocator as a lazy
+// singleton for better testability and maintainability.
+//
+// Usage:
+// final notificationService = getService<NotificationService>();
+// await notificationService.initialize();
+//
+// DO NOT instantiate directly. Always use ServiceLocator.get<NotificationService>()
+// or the convenience function getService<NotificationService>().
import 'dart:developer' as developer;
@@ -36,12 +48,6 @@ void flutterLocalNotificationsBackgroundHandler(
// **FIN DE MODIFICACIÓN**
class NotificationService {
- static final NotificationService _instance = NotificationService._internal();
-
- factory NotificationService() => _instance;
-
- NotificationService._internal();
-
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
----------------------------------------
📄 ARCHIVO: lib/services/service_locator.dart
Estado: modified (+7/-0)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Fservice_locator.dart
DIFF:
----------------------------------------
@@ -16,6 +16,7 @@ library;
import 'package:devocional_nuevo/services/analytics_service.dart';
import 'package:devocional_nuevo/services/localization_service.dart';
+import 'package:devocional_nuevo/services/notification_service.dart';
import 'package:devocional_nuevo/services/tts/i_tts_service.dart';
import 'package:devocional_nuevo/services/tts/voice_settings_service.dart';
import 'package:devocional_nuevo/services/tts_service.dart';
@@ -108,6 +109,12 @@ void setupServiceLocator() {
// This service tracks user events and behaviors using Firebase Analytics
locator.registerLazySingleton<AnalyticsService>(() => AnalyticsService());
+ // Register NotificationService as a lazy singleton (created when first accessed)
+ // This service manages FCM, local notifications, and notification settings
+ // Migrated from singleton pattern to DI for better testability and maintainability
+ locator
+ .registerLazySingleton<NotificationService>(() => NotificationService());
+
// Add more service registrations here as needed
// Example:
// locator.registerFactory<IApiClient>(() => ApiClient());
----------------------------------------
📄 ARCHIVO: lib/services/tts/voice_settings_service.dart
Estado: modified (+1/-1)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/lib%2Fservices%2Ftts%2Fvoice_settings_service.dart
DIFF:
----------------------------------------
@@ -245,7 +245,7 @@ class VoiceSettingsService {
Future<String?> loadSavedVoice(String language) async {
try {
final prefs = await SharedPreferences.getInstance();
- final savedVoice = prefs.getString('tts_voice_' + language);
+ final savedVoice = prefs.getString('tts_voice_$language');
if (savedVoice != null) {
// Parse del formato legacy o nuevo
----------------------------------------
📄 ARCHIVO: test/critical_coverage/notification_service_working_test.dart
Estado: modified (+12/-0)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Fcritical_coverage%2Fnotification_service_working_test.dart
DIFF:
----------------------------------------
@@ -3,6 +3,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:devocional_nuevo/services/notification_service.dart';
+import 'package:devocional_nuevo/services/service_locator.dart';
void main() {
group('NotificationService Critical Business Logic Tests', () {
@@ -13,6 +14,17 @@ void main() {
setUp(() {
// Initialize SharedPreferences mock
SharedPreferences.setMockInitialValues({});
+
+ // Reset and setup ServiceLocator for testing
+ ServiceLocator().reset();
+ ServiceLocator().registerLazySingleton<NotificationService>(
+ () => NotificationService(),
+ );
+ });
+
+ tearDown(() {
+ // Clean up ServiceLocator after each test
+ ServiceLocator().reset();
});
test('should validate notification time format correctly', () {
----------------------------------------
📄 ARCHIVO: test/integration/chinese_user_journey_test.dart
Estado: modified (+0/-3)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Fintegration%2Fchinese_user_journey_test.dart
DIFF:
----------------------------------------
@@ -1,13 +1,10 @@
import 'package:devocional_nuevo/providers/localization_provider.dart';
-import 'package:devocional_nuevo/services/localization_service.dart';
import 'package:devocional_nuevo/services/service_locator.dart';
import 'package:devocional_nuevo/services/tts/bible_text_formatter.dart';
import 'package:devocional_nuevo/services/tts/voice_settings_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
-import '../helpers/test_helpers.dart';
-
void main() {
group('Chinese Language - Complete User Journey Tests', () {
late LocalizationProvider provider;
----------------------------------------
📄 ARCHIVO: test/unit/services/service_locator_test.dart
Estado: modified (+17/-0)
Raw URL: https://github.com/develop4God/Devocional_nuevo/raw/223dd7055e3e654cb908abc79080e6706230a1d6/test%2Funit%2Fservices%2Fservice_locator_test.dart
DIFF:
----------------------------------------
@@ -1,3 +1,4 @@
+import 'package:devocional_nuevo/services/notification_service.dart';
import 'package:devocional_nuevo/services/service_locator.dart';
import 'package:devocional_nuevo/services/tts/voice_settings_service.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -136,5 +137,21 @@ void main() {
expect(identical(getService<VoiceSettingsService>(), mock), isTrue);
});
});
+
+ group('NotificationService Registration', () {
+ test('NotificationService can be registered and verified', () {
+ // Register NotificationService as lazy singleton
+ ServiceLocator().registerLazySingleton<NotificationService>(
+ () => NotificationService(),
+ );
+
+ // Verify it's registered
+ expect(ServiceLocator().isRegistered<NotificationService>(), isTrue);
+
+ // Clean up to avoid instantiation issues (Firebase not initialized in test)
+ ServiceLocator().unregister<NotificationService>();
+ expect(ServiceLocator().isRegistered<NotificationService>(), isFalse);
+ });
+ });
});
}
----------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment