Skip to content

Commit f6e64dc

Browse files
authored
Merge pull request #75 from flutter-news-app-full-source-code/fix/init
Fix/init
2 parents 913659b + 8fc6336 commit f6e64dc

File tree

2 files changed

+56
-15
lines changed

2 files changed

+56
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Upcoming Release
4+
5+
- **fix**: prevent race condition during concurrent dependency initialization.
6+
37
## 1.0.1 - 2025-10-17
48

59
- **chore**: A new migration ensures that existing user preference documents are updated to include the savedFilters field, initialized as an empty array.

lib/src/config/app_dependencies.dart

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// ignore_for_file: public_member_api_docs
22

3+
import 'dart:async';
34
import 'package:core/core.dart';
45
import 'package:data_mongodb/data_mongodb.dart';
56
import 'package:data_repository/data_repository.dart';
@@ -40,11 +41,12 @@ class AppDependencies {
4041
/// Provides access to the singleton instance.
4142
static AppDependencies get instance => _instance;
4243

43-
bool _isInitialized = false;
44-
Object? _initializationError;
45-
StackTrace? _initializationStackTrace;
4644
final _log = Logger('AppDependencies');
4745

46+
// A Completer to manage the one-time asynchronous initialization.
47+
// This ensures the initialization logic runs only once.
48+
Completer<void>? _initCompleter;
49+
4850
// --- Late-initialized fields for all dependencies ---
4951

5052
// Database
@@ -79,16 +81,30 @@ class AppDependencies {
7981
/// Initializes all application dependencies.
8082
///
8183
/// This method is idempotent; it will only run the initialization logic once.
82-
Future<void> init() async {
83-
// If initialization previously failed, re-throw the original error.
84-
if (_initializationError != null) {
85-
return Future.error(_initializationError!, _initializationStackTrace);
84+
Future<void> init() {
85+
// If _initCompleter is not null, it means initialization is either in
86+
// progress or has already completed. Return its future to allow callers
87+
// to await the single, shared initialization process.
88+
if (_initCompleter != null) {
89+
return _initCompleter!.future;
8690
}
8791

88-
if (_isInitialized) return;
92+
// This is the first call to init(). Create the completer and start the
93+
// initialization process.
94+
_initCompleter = Completer<void>();
95+
_log.info('Starting application dependency initialization...');
96+
// We intentionally don't await this future here. The completer's future,
97+
// which is returned below, is what callers will await.
98+
unawaited(_initializeDependencies());
8999

90-
_log.info('Initializing application dependencies...');
100+
// Return the future from the completer.
101+
return _initCompleter!.future;
102+
}
91103

104+
/// The core logic for initializing all dependencies.
105+
/// This method is private and should only be called once by [init].
106+
Future<void> _initializeDependencies() async {
107+
_log.info('Initializing application dependencies...');
92108
try {
93109
// 1. Initialize Database Connection
94110
_mongoDbConnectionManager = MongoDbConnectionManager();
@@ -271,24 +287,45 @@ class AppDependencies {
271287
cacheDuration: EnvironmentConfig.countryServiceCacheDuration,
272288
);
273289

274-
_isInitialized = true;
275290
_log.info('Application dependencies initialized successfully.');
291+
// Signal that initialization has completed successfully.
292+
_initCompleter!.complete();
276293
} catch (e, s) {
277294
_log.severe('Failed to initialize application dependencies', e, s);
278-
_initializationError = e;
279-
_initializationStackTrace = s;
295+
// Signal that initialization has failed.
296+
_initCompleter!.completeError(e, s);
280297
rethrow;
281298
}
282299
}
283300

284301
/// Disposes of resources, such as closing the database connection.
285302
Future<void> dispose() async {
286-
if (!_isInitialized) return;
303+
// Only attempt to dispose if initialization has been started.
304+
if (_initCompleter == null) {
305+
_log.info('Dispose called, but dependencies were never initialized.');
306+
return;
307+
}
308+
309+
// Wait for initialization to complete before disposing resources.
310+
// This prevents a race condition if dispose() is called during init().
311+
try {
312+
await _initCompleter!.future;
313+
} catch (_) {
314+
// Initialization may have failed, but we still proceed to dispose
315+
// any partially initialized resources.
316+
_log.warning(
317+
'Disposing dependencies after a failed initialization attempt.',
318+
);
319+
}
320+
321+
_log.info('Disposing application dependencies...');
287322
await _mongoDbConnectionManager.close();
288323
tokenBlacklistService.dispose();
289324
rateLimitService.dispose();
290325
countryQueryService.dispose(); // Dispose the new service
291-
_isInitialized = false;
292-
_log.info('Application dependencies disposed.');
326+
327+
// Reset the completer to allow for re-initialization (e.g., in tests).
328+
_initCompleter = null;
329+
_log.info('Application dependencies disposed and state reset.');
293330
}
294331
}

0 commit comments

Comments
 (0)