@@ -21,6 +21,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/app/services/dem
2121import 'package:flutter_news_app_mobile_client_full_source_code/bloc_observer.dart' ;
2222import 'package:flutter_news_app_mobile_client_full_source_code/shared/data/clients/country_inmemory_client.dart' ;
2323import 'package:http_client/http_client.dart' ;
24+ import 'package:kv_storage_service/kv_storage_service.dart' ;
2425import 'package:kv_storage_shared_preferences/kv_storage_shared_preferences.dart' ;
2526import 'package:logging/logging.dart' ;
2627import 'package:timeago/timeago.dart' as timeago;
@@ -32,22 +33,57 @@ Future<Widget> bootstrap(
3233) async {
3334 WidgetsFlutterBinding .ensureInitialized ();
3435 Bloc .observer = const AppBlocObserver ();
35-
36+ final logger = Logger ( 'bootstrap' );
3637 timeago.setLocaleMessages ('en' , EnTimeagoMessages ());
3738 timeago.setLocaleMessages ('ar' , ArTimeagoMessages ());
3839
39- final logger = Logger ('bootstrap' );
40-
40+ // 1. Initialize KV Storage Service first, as it's a foundational dependency.
4141 final kvStorage = await KVStorageSharedPreferences .getInstance ();
4242
43+ // 2. Conditionally initialize HttpClient and Auth services based on environment.
44+ // This ensures HttpClient is available before any DataApi or AdProvider
45+ // that depends on it.
4346 late final AuthClient authClient;
4447 late final AuthRepository authenticationRepository;
45- HttpClient ? httpClient;
48+ late final HttpClient httpClient;
49+ if (appConfig.environment == app_config.AppEnvironment .demo) {
50+ // In-memory authentication for demo environment.
51+ authClient = AuthInmemory ();
52+ authenticationRepository = AuthRepository (
53+ authClient: authClient,
54+ storageService: kvStorage,
55+ );
56+ // For demo, httpClient is not strictly needed for DataApi,
57+ // but we initialize a dummy one to satisfy non-nullable requirements
58+ // if any part of the code path expects it.
59+ // In a real scenario, DataApi would not be used in demo mode.
60+ httpClient = HttpClient (
61+ baseUrl: appConfig.baseUrl,
62+ tokenProvider: () async => null , // No token needed for demo
63+ logger: logger,
64+ );
65+ } else {
66+ // For production and development environments, an HTTP client is needed.
67+ // Initialize HttpClient first. Its tokenProvider now directly reads from
68+ // kvStorage, breaking the circular dependency with AuthRepository.
69+ httpClient = HttpClient (
70+ baseUrl: appConfig.baseUrl,
71+ tokenProvider: () =>
72+ kvStorage.readString (key: StorageKey .authToken.stringValue),
73+ logger: logger,
74+ );
4675
47- // Initialize AdProvider and AdService
48- // Initialize AdProvider based on platform.
49- // On web, use a No-Op provider to prevent MissingPluginException,
50- // as Google Mobile Ads SDK does not support native ads on web.
76+ // Now that httpClient is available, initialize AuthApi and AuthRepository.
77+ authClient = AuthApi (httpClient: httpClient);
78+ authenticationRepository = AuthRepository (
79+ authClient: authClient,
80+ storageService: kvStorage,
81+ );
82+ }
83+
84+ // 3. Initialize AdProvider and AdService.
85+ // These now have a guaranteed valid httpClient (for DataApi-based LocalAdProvider)
86+ // or can proceed independently (AdMobAdProvider).
5187 final adProviders = < AdPlatformType , AdProvider > {
5288 AdPlatformType .admob: AdMobAdProvider (logger: logger),
5389 AdPlatformType .local: LocalAdProvider (
@@ -60,7 +96,8 @@ Future<Widget> bootstrap(
6096 logger: logger,
6197 )
6298 : DataApi <LocalAd >(
63- httpClient: httpClient! ,
99+ httpClient:
100+ httpClient, // httpClient is now guaranteed to be non-null
64101 modelName: 'local_ad' ,
65102 fromJson: LocalAd .fromJson,
66103 toJson: LocalAd .toJson,
@@ -72,33 +109,14 @@ Future<Widget> bootstrap(
72109 };
73110
74111 final adService = AdService (adProviders: adProviders, logger: logger);
75- await adService.initialize (); // Initialize all selected AdProviders early
76-
77- if (appConfig.environment == app_config.AppEnvironment .demo) {
78- authClient = AuthInmemory ();
79- authenticationRepository = AuthRepository (
80- authClient: authClient,
81- storageService: kvStorage,
82- );
83- } else {
84- // For production and development environments, an HTTP client is needed.
85- httpClient = HttpClient (
86- baseUrl: appConfig.baseUrl,
87- tokenProvider: () => authenticationRepository.getAuthToken (),
88- logger: logger,
89- );
90- authClient = AuthApi (httpClient: httpClient);
91- authenticationRepository = AuthRepository (
92- authClient: authClient,
93- storageService: kvStorage,
94- );
95- }
112+ await adService.initialize ();
96113
97114 // Fetch the initial user from the authentication repository.
98115 // This ensures the AppBloc starts with an accurate authentication status.
99116 final initialUser = await authenticationRepository.getCurrentUser ();
100117
101- // Conditional data client instantiation based on environment
118+ // 4. Initialize all other DataClients and Repositories.
119+ // These now also have a guaranteed valid httpClient.
102120 DataClient <Headline > headlinesClient;
103121 DataClient <Topic > topicsClient;
104122 DataClient <Country > countriesClient;
@@ -107,8 +125,7 @@ Future<Widget> bootstrap(
107125 DataClient <UserAppSettings > userAppSettingsClient;
108126 DataClient <RemoteConfig > remoteConfigClient;
109127 DataClient <User > userClient;
110- DataClient <LocalAd > localAdClient; // Declare localAdClient
111-
128+ DataClient <LocalAd > localAdClient;
112129 if (appConfig.environment == app_config.AppEnvironment .demo) {
113130 headlinesClient = DataInMemory <Headline >(
114131 toJson: (i) => i.toJson (),
@@ -192,7 +209,7 @@ Future<Widget> bootstrap(
192209 );
193210 } else if (appConfig.environment == app_config.AppEnvironment .development) {
194211 headlinesClient = DataApi <Headline >(
195- httpClient: httpClient! ,
212+ httpClient: httpClient,
196213 modelName: 'headline' ,
197214 fromJson: Headline .fromJson,
198215 toJson: (headline) => headline.toJson (),
@@ -257,7 +274,7 @@ Future<Widget> bootstrap(
257274 } else {
258275 // Default to API clients for production
259276 headlinesClient = DataApi <Headline >(
260- httpClient: httpClient! ,
277+ httpClient: httpClient,
261278 modelName: 'headline' ,
262279 fromJson: Headline .fromJson,
263280 toJson: (headline) => headline.toJson (),
0 commit comments