Skip to content

Commit 8486354

Browse files
committed
refactor(app): implement stable startup with full-screen status pages
- Introduce a new stable startup architecture using BlocBuilder - Add full-screen status pages for maintenance, update required, and config fetching states - Move main app UI build to a more stable point in the app lifecycle - Simplify theme and localization setup in MaterialApp - Update StatusPage to use new localization approach
1 parent 236768b commit 8486354

File tree

2 files changed

+46
-110
lines changed

2 files changed

+46
-110
lines changed

lib/app/view/app.dart

Lines changed: 43 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/app/config/app_e
99
import 'package:flutter_news_app_mobile_client_full_source_code/app/services/app_status_service.dart';
1010
import 'package:flutter_news_app_mobile_client_full_source_code/app/services/demo_data_migration_service.dart';
1111
import 'package:flutter_news_app_mobile_client_full_source_code/authentication/bloc/authentication_bloc.dart';
12-
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/app_localizations.dart';
12+
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
1313
import 'package:flutter_news_app_mobile_client_full_source_code/router/router.dart';
1414
import 'package:flutter_news_app_mobile_client_full_source_code/status/view/view.dart';
1515
import 'package:go_router/go_router.dart';
@@ -191,132 +191,68 @@ class _AppViewState extends State<_AppView> {
191191
listener: (context, state) {
192192
_statusNotifier.value = state.status;
193193
},
194+
// The BlocBuilder is the core of the new stable startup architecture.
195+
// It acts as a high-level switch that determines which UI to show based
196+
// on the application's status.
194197
child: BlocBuilder<AppBloc, AppState>(
195-
// Rebuild the UI based on AppBloc's state (theme, locale, and critical app statuses)
196198
builder: (context, state) {
197-
// Defer l10n access until inside a MaterialApp context
199+
// --- Full-Screen Status Pages ---
200+
// The following states represent critical, app-wide conditions that
201+
// must be handled before the main router and UI are displayed.
202+
// By returning a dedicated widget here, we ensure these pages are
203+
// full-screen and exist outside the main app's navigation shell.
198204

199-
// Handle critical RemoteConfig loading states globally
200-
// These checks have the highest priority and will lock the entire UI.
201-
//
202-
// Check for Maintenance Mode.
203205
if (state.status == AppStatus.underMaintenance) {
204-
return const MaterialApp(
206+
// The app is in maintenance mode. Show the MaintenancePage.
207+
// It's wrapped in a basic MaterialApp to provide theme and l10n.
208+
return MaterialApp(
205209
debugShowCheckedModeBanner: false,
206-
home: MaintenancePage(),
210+
theme: lightTheme(),
211+
darkTheme: darkTheme(),
212+
themeMode: state.themeMode,
213+
localizationsDelegates: AppLocalizations.localizationsDelegates,
214+
supportedLocales: AppLocalizations.supportedLocales,
215+
home: const MaintenancePage(),
207216
);
208217
}
209218

210-
// Check for a Required Update.
211219
if (state.status == AppStatus.updateRequired) {
212-
return const MaterialApp(
213-
debugShowCheckedModeBanner: false,
214-
home: UpdateRequiredPage(),
215-
);
216-
}
217-
218-
// Check for Config Fetching state.
219-
if (state.status == AppStatus.configFetching) {
220+
// A mandatory update is required. Show the UpdateRequiredPage.
220221
return MaterialApp(
221222
debugShowCheckedModeBanner: false,
222-
theme: lightTheme(
223-
scheme: FlexScheme.material,
224-
appTextScaleFactor: AppTextScaleFactor.medium,
225-
appFontWeight: AppFontWeight.regular,
226-
fontFamily: null,
227-
),
228-
darkTheme: darkTheme(
229-
scheme: FlexScheme.material,
230-
appTextScaleFactor: AppTextScaleFactor.medium,
231-
appFontWeight: AppFontWeight.regular,
232-
fontFamily: null, // System default font
233-
),
234-
themeMode: state
235-
.themeMode, // Still respect light/dark if available from system
236-
localizationsDelegates: const [
237-
...AppLocalizations.localizationsDelegates,
238-
...UiKitLocalizations.localizationsDelegates,
239-
],
240-
supportedLocales: const [
241-
...AppLocalizations.supportedLocales,
242-
...UiKitLocalizations.supportedLocales,
243-
],
244-
home: Scaffold(
245-
body: Builder(
246-
// Use Builder to get context under MaterialApp
247-
builder: (innerContext) {
248-
return LoadingStateWidget(
249-
icon: Icons.settings_applications_outlined,
250-
headline: AppLocalizations.of(
251-
innerContext,
252-
).headlinesFeedLoadingHeadline,
253-
subheadline: AppLocalizations.of(innerContext).pleaseWait,
254-
);
255-
},
256-
),
257-
),
223+
theme: lightTheme(),
224+
darkTheme: darkTheme(),
225+
themeMode: state.themeMode,
226+
localizationsDelegates: AppLocalizations.localizationsDelegates,
227+
supportedLocales: AppLocalizations.supportedLocales,
228+
home: const UpdateRequiredPage(),
258229
);
259230
}
260231

261-
if (state.status == AppStatus.configFetchFailed) {
232+
if (state.status == AppStatus.configFetching ||
233+
state.status == AppStatus.configFetchFailed) {
234+
// The app is in the process of fetching its initial remote
235+
// configuration or has failed to do so. The StatusPage handles
236+
// both the loading indicator and the retry mechanism.
262237
return MaterialApp(
263238
debugShowCheckedModeBanner: false,
264-
theme: lightTheme(
265-
scheme: FlexScheme.material,
266-
appTextScaleFactor: AppTextScaleFactor.medium,
267-
appFontWeight: AppFontWeight.regular,
268-
fontFamily: null,
269-
),
270-
darkTheme: darkTheme(
271-
scheme: FlexScheme.material,
272-
appTextScaleFactor: AppTextScaleFactor.medium,
273-
appFontWeight: AppFontWeight.regular,
274-
fontFamily: null,
275-
),
239+
theme: lightTheme(),
240+
darkTheme: darkTheme(),
276241
themeMode: state.themeMode,
277-
localizationsDelegates: const [
278-
...AppLocalizations.localizationsDelegates,
279-
...UiKitLocalizations.localizationsDelegates,
280-
],
281-
supportedLocales: const [
282-
...AppLocalizations.supportedLocales,
283-
...UiKitLocalizations.supportedLocales,
284-
],
285-
home: Scaffold(
286-
body: Builder(
287-
// Use Builder to get context under MaterialApp
288-
builder: (innerContext) {
289-
return FailureStateWidget(
290-
exception: const NetworkException(),
291-
retryButtonText: UiKitLocalizations.of(
292-
innerContext,
293-
)!.retryButtonText,
294-
onRetry: () {
295-
// Use outer context for BLoC access
296-
context.read<AppBloc>().add(
297-
const AppConfigFetchRequested(),
298-
);
299-
},
300-
);
301-
},
302-
),
303-
),
242+
localizationsDelegates: AppLocalizations.localizationsDelegates,
243+
supportedLocales: AppLocalizations.supportedLocales,
244+
home: const StatusPage(),
304245
);
305246
}
306247

307-
// If config is loaded (or not in a failed/fetching state for config), proceed with main app UI
308-
// It's safe to access l10n here if needed for print statements,
309-
// as this path implies we are about to build the main MaterialApp.router
310-
// which provides localizations.
311-
// final l10n = context.l10n;
312-
print('[_AppViewState] Building MaterialApp.router');
313-
print('[_AppViewState] state.fontFamily: ${state.fontFamily}');
314-
print(
315-
'[_AppViewState] state.settings.displaySettings.fontFamily: ${state.settings.displaySettings.fontFamily}',
316-
);
317-
print(
318-
'[_AppViewState] state.settings.displaySettings.fontWeight: ${state.settings.displaySettings.fontWeight}',
319-
);
248+
// --- Main Application UI ---
249+
// If none of the critical states above are met, the app is ready
250+
// to display its main UI. We build the MaterialApp.router here.
251+
//
252+
// This is the STABLE root of the main application. Because it is
253+
// only built when the app is in a "running" state, it will not be
254+
// destroyed and rebuilt during startup, which fixes the
255+
// `BuildContext` instability and related crashes.
320256
return MaterialApp.router(
321257
debugShowCheckedModeBanner: false,
322258
themeMode: state.themeMode,

lib/status/view/status_page.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:core/core.dart' hide AppStatus;
1+
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
@@ -24,7 +24,7 @@ class StatusPage extends StatelessWidget {
2424
return Scaffold(
2525
body: BlocBuilder<AppBloc, AppState>(
2626
builder: (context, state) {
27-
final l10n = AppLocalizationsX(context).l10n;
27+
final l10n = context.l10n;
2828

2929
if (state.status == AppStatus.configFetching) {
3030
// While fetching configuration, display a clear loading indicator.
@@ -40,7 +40,7 @@ class StatusPage extends StatelessWidget {
4040
// This allows the user to recover from transient network issues.
4141
return FailureStateWidget(
4242
exception: const NetworkException(), // A generic network error
43-
retryButtonText: 'l10n.retryButtonText', //TODO(fulleni): localize me.
43+
retryButtonText: l10n.retryButtonText,
4444
onRetry: () {
4545
// Dispatch the event to AppBloc to re-trigger the fetch.
4646
context.read<AppBloc>().add(const AppConfigFetchRequested());

0 commit comments

Comments
 (0)