Skip to content

Commit 81485d3

Browse files
authored
Merge pull request #143 from flutter-news-app-full-source-code/refactor/improve-logging
Refactor/improve logging
2 parents 7b62181 + 6047377 commit 81485d3

File tree

4 files changed

+124
-35
lines changed

4 files changed

+124
-35
lines changed

lib/app/view/app.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/router/router.da
1818
import 'package:flutter_news_app_mobile_client_full_source_code/status/view/view.dart';
1919
import 'package:go_router/go_router.dart';
2020
import 'package:kv_storage_service/kv_storage_service.dart';
21+
import 'package:logging/logging.dart';
2122
import 'package:ui_kit/ui_kit.dart';
2223

2324
/// {@template app_widget}
@@ -223,6 +224,7 @@ class _AppViewState extends State<_AppView> {
223224
late final GoRouter _router;
224225
late final ValueNotifier<AppLifeCycleStatus> _statusNotifier;
225226
AppStatusService? _appStatusService;
227+
final _routerLogger = Logger('GoRouter');
226228

227229
@override
228230
void initState() {
@@ -257,6 +259,7 @@ class _AppViewState extends State<_AppView> {
257259
adService: widget.adService,
258260
navigatorKey: widget.navigatorKey,
259261
inlineAdCacheService: widget.inlineAdCacheService,
262+
logger: _routerLogger,
260263
);
261264
}
262265

lib/bootstrap.dart

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,45 @@ Future<Widget> bootstrap(
3939
app_config.AppConfig appConfig,
4040
app_config.AppEnvironment environment,
4141
) async {
42+
// Setup logging
43+
Logger.root.level = environment == app_config.AppEnvironment.production
44+
? Level.INFO
45+
: Level.ALL;
46+
47+
Logger.root.onRecord.listen((record) {
48+
final message = StringBuffer(
49+
'${record.level.name}: ${record.time}: ${record.loggerName}: ${record.message}',
50+
);
51+
if (record.error != null) {
52+
message.write('\nError: ${record.error}');
53+
}
54+
if (record.stackTrace != null) {
55+
message.write('\nStack Trace: ${record.stackTrace}');
56+
}
57+
print(message);
58+
});
59+
60+
final logger = Logger('bootstrap')
61+
..config('--- Starting Bootstrap Process ---')
62+
..config('App Environment: $environment');
63+
4264
WidgetsFlutterBinding.ensureInitialized();
4365
Bloc.observer = const AppBlocObserver();
44-
final logger = Logger('bootstrap');
4566
timeago.setLocaleMessages('en', EnTimeagoMessages());
4667
timeago.setLocaleMessages('ar', ArTimeagoMessages());
4768

69+
logger.info('1. Initializing KV Storage Service...');
4870
// 1. Initialize KV Storage Service first, as it's a foundational dependency.
4971
final kvStorage = await KVStorageSharedPreferences.getInstance();
72+
logger.fine('KV Storage Service initialized (SharedPreferences).');
5073

5174
// Initialize InlineAdCacheService early as it's a singleton and needs AdService.
5275
// It will be fully configured once AdService is available.
5376
InlineAdCacheService? inlineAdCacheService;
5477

5578
// 2. Initialize HttpClient. Its tokenProvider now directly reads from
5679
// kvStorage, breaking the circular dependency with AuthRepository.
80+
logger.info('2. Initializing HttpClient...');
5781
// This HttpClient instance is used for all subsequent API calls, including
5882
// the initial unauthenticated fetch of RemoteConfig.
5983
final httpClient = HttpClient(
@@ -62,18 +86,22 @@ Future<Widget> bootstrap(
6286
kvStorage.readString(key: StorageKey.authToken.stringValue),
6387
logger: logger,
6488
);
65-
66-
// 3. Initialize RemoteConfigClient and Repository, and fetch RemoteConfig.
89+
logger
90+
..fine('HttpClient initialized for base URL: ${appConfig.baseUrl}')
91+
// 3. Initialize RemoteConfigClient and Repository, and fetch RemoteConfig.
92+
..info('3. Initializing RemoteConfig client and repository...');
6793
// This is done early because RemoteConfig is now publicly accessible (unauthenticated).
6894
late DataClient<RemoteConfig> remoteConfigClient;
6995
if (appConfig.environment == app_config.AppEnvironment.demo) {
96+
logger.fine('Using in-memory client for RemoteConfig.');
7097
remoteConfigClient = DataInMemory<RemoteConfig>(
7198
toJson: (i) => i.toJson(),
7299
getId: (i) => i.id,
73100
initialData: remoteConfigsFixturesData,
74101
logger: logger,
75102
);
76103
} else {
104+
logger.fine('Using API client for RemoteConfig.');
77105
// For development and production environments, use DataApi.
78106
remoteConfigClient = DataApi<RemoteConfig>(
79107
httpClient: httpClient,
@@ -86,60 +114,62 @@ Future<Widget> bootstrap(
86114
final remoteConfigRepository = DataRepository<RemoteConfig>(
87115
dataClient: remoteConfigClient,
88116
);
117+
logger.fine('RemoteConfig repository initialized.');
89118

90119
// Fetch the initial RemoteConfig. This is a critical step to determine
91120
// the app's global status (e.g., maintenance mode, update required)
92121
// before proceeding with other initializations.
93122
RemoteConfig? initialRemoteConfig;
94123
HttpException? initialRemoteConfigError;
95124

125+
logger.info('4. Fetching initial RemoteConfig...');
96126
try {
97127
initialRemoteConfig = await remoteConfigRepository.read(
98128
id: kRemoteConfigId,
99129
);
100-
logger.info('[bootstrap] Initial RemoteConfig fetched successfully.');
130+
logger.fine('Initial RemoteConfig fetched successfully.');
101131
} on HttpException catch (e) {
102-
logger.severe(
103-
'[bootstrap] Failed to fetch initial RemoteConfig (HttpException): $e',
104-
);
132+
logger.severe('Failed to fetch initial RemoteConfig (HttpException): $e');
105133
initialRemoteConfigError = e;
106134
} catch (e, s) {
107-
logger.severe(
108-
'[bootstrap] Unexpected error fetching initial RemoteConfig.',
109-
e,
110-
s,
111-
);
135+
logger.severe('Unexpected error fetching initial RemoteConfig.', e, s);
112136
initialRemoteConfigError = UnknownException(e.toString());
113137
}
114138

115139
// 4. Conditionally initialize Auth services based on environment.
116140
// This is done after RemoteConfig is fetched, as Auth services might depend
117-
// on configurations defined in RemoteConfig (though not directly in this case).
141+
// on configurations defined in RemoteConfig.
142+
logger.info('5. Initializing Authentication services...');
118143
late final AuthClient authClient;
119144
late final AuthRepository authenticationRepository;
120145
if (appConfig.environment == app_config.AppEnvironment.demo) {
146+
logger.fine('Using in-memory client for Authentication.');
121147
// In-memory authentication for demo environment.
122148
authClient = AuthInmemory();
123149
authenticationRepository = AuthRepository(
124150
authClient: authClient,
125151
storageService: kvStorage,
126152
);
127153
} else {
154+
logger.fine('Using API client for Authentication.');
128155
// Now that httpClient is available, initialize AuthApi and AuthRepository.
129156
authClient = AuthApi(httpClient: httpClient);
130157
authenticationRepository = AuthRepository(
131158
authClient: authClient,
132159
storageService: kvStorage,
133160
);
134161
}
135-
136-
// 5. Initialize AdProvider and AdService.
162+
logger
163+
..fine('Authentication repository initialized.')
164+
// 5. Initialize AdProvider and AdService.
165+
..info('6. Initializing Ad providers and AdService...');
137166
late final Map<AdPlatformType, AdProvider> adProviders;
138167

139168
// Conditionally instantiate ad providers based on the application environment.
140169
// This ensures that only the relevant ad providers are available for the
141170
// current environment, preventing unintended usage.
142171
if (appConfig.environment == app_config.AppEnvironment.demo || kIsWeb) {
172+
logger.fine('Using DemoAdProvider for all ad platforms.');
143173
final demoAdProvider = DemoAdProvider(logger: logger);
144174
adProviders = {
145175
// In the demo environment or on the web, all ad platform types map to
@@ -151,6 +181,7 @@ Future<Widget> bootstrap(
151181
AdPlatformType.demo: demoAdProvider,
152182
};
153183
} else {
184+
logger.fine('Using AdMobAdProvider and LocalAdProvider.');
154185
// For development and production environments (non-web), use real ad providers.
155186
adProviders = {
156187
// AdMob provider for Google Mobile Ads.
@@ -180,22 +211,28 @@ Future<Widget> bootstrap(
180211
logger: logger,
181212
);
182213
await adService.initialize();
214+
logger.fine('AdService initialized.');
183215

184216
// Initialize InlineAdCacheService with the created AdService.
185217
inlineAdCacheService = InlineAdCacheService(adService: adService);
186-
187-
// Fetch the initial user from the authentication repository.
188-
// This ensures the AppBloc starts with an accurate authentication status.
218+
logger
219+
..fine('InlineAdCacheService initialized.')
220+
// Fetch the initial user from the authentication repository.
221+
// This ensures the AppBloc starts with an accurate authentication status.
222+
..info('7. Fetching initial user...');
189223
final initialUser = await authenticationRepository.getCurrentUser();
224+
logger.fine('Initial user fetched: ${initialUser?.id ?? 'none'}.');
190225

191226
// Create a GlobalKey for the NavigatorState to be used by AppBloc
192227
// and InterstitialAdManager for BuildContext access.
193228
final navigatorKey = GlobalKey<NavigatorState>();
194229

195230
// Initialize PackageInfoService
196231
final packageInfoService = PackageInfoServiceImpl(logger: logger);
197-
198-
// 6. Initialize all other DataClients and Repositories.
232+
logger
233+
..fine('PackageInfoService initialized.')
234+
// 6. Initialize all other DataClients and Repositories.
235+
..info('8. Initializing Data clients and repositories...');
199236
// These now also have a guaranteed valid httpClient.
200237
late final DataClient<Headline> headlinesClient;
201238
late final DataClient<Topic> topicsClient;
@@ -206,6 +243,7 @@ Future<Widget> bootstrap(
206243
late final DataClient<User> userClient;
207244
late final DataClient<LocalAd> localAdClient;
208245
if (appConfig.environment == app_config.AppEnvironment.demo) {
246+
logger.fine('Using in-memory clients for all data repositories.');
209247
headlinesClient = DataInMemory<Headline>(
210248
toJson: (i) => i.toJson(),
211249
getId: (i) => i.id,
@@ -280,6 +318,7 @@ Future<Widget> bootstrap(
280318
logger: logger,
281319
);
282320
} else if (appConfig.environment == app_config.AppEnvironment.development) {
321+
logger.fine('Using API clients for all data repositories (Development).');
283322
headlinesClient = DataApi<Headline>(
284323
httpClient: httpClient,
285324
modelName: 'headline',
@@ -337,6 +376,7 @@ Future<Widget> bootstrap(
337376
logger: logger,
338377
);
339378
} else {
379+
logger.fine('Using API clients for all data repositories (Production).');
340380
// Default to API clients for production
341381
headlinesClient = DataApi<Headline>(
342382
httpClient: httpClient,
@@ -395,6 +435,7 @@ Future<Widget> bootstrap(
395435
logger: logger,
396436
);
397437
}
438+
logger.fine('All data clients instantiated.');
398439

399440
final headlinesRepository = DataRepository<Headline>(
400441
dataClient: headlinesClient,
@@ -413,6 +454,7 @@ Future<Widget> bootstrap(
413454
dataClient: userAppSettingsClient,
414455
);
415456
final userRepository = DataRepository<User>(dataClient: userClient);
457+
logger.fine('All data repositories initialized.');
416458

417459
// Conditionally instantiate DemoDataMigrationService
418460
final demoDataMigrationService =
@@ -422,6 +464,9 @@ Future<Widget> bootstrap(
422464
userContentPreferencesRepository: userContentPreferencesRepository,
423465
)
424466
: null;
467+
logger.fine(
468+
'DemoDataMigrationService initialized: ${demoDataMigrationService != null}',
469+
);
425470

426471
// Conditionally instantiate DemoDataInitializerService
427472
// This service is responsible for ensuring that essential user-specific data
@@ -435,7 +480,11 @@ Future<Widget> bootstrap(
435480
userContentPreferencesRepository: userContentPreferencesRepository,
436481
)
437482
: null;
438-
483+
logger
484+
..fine(
485+
'DemoDataInitializerService initialized: ${demoDataInitializerService != null}',
486+
)
487+
..info('--- Bootstrap Process Complete. Returning App widget. ---');
439488
return App(
440489
authenticationRepository: authenticationRepository,
441490
headlinesRepository: headlinesRepository,

lib/router/go_router_observer.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:logging/logging.dart';
3+
4+
class GoRouterObserver extends NavigatorObserver {
5+
GoRouterObserver({required this.logger});
6+
7+
final Logger logger;
8+
9+
@override
10+
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
11+
logger.info(
12+
'Pushed: ${route.settings.name} | from: ${previousRoute?.settings.name}',
13+
);
14+
}
15+
16+
@override
17+
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
18+
logger.info(
19+
'Popped: ${route.settings.name} | to: ${previousRoute?.settings.name}',
20+
);
21+
}
22+
23+
@override
24+
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
25+
logger.info(
26+
'Removed: ${route.settings.name} | previous: ${previousRoute?.settings.name}',
27+
);
28+
}
29+
30+
@override
31+
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
32+
logger.info(
33+
'Replaced: ${oldRoute?.settings.name} | with: ${newRoute?.settings.name}',
34+
);
35+
}
36+
}

0 commit comments

Comments
 (0)