Skip to content

Commit 716a7c0

Browse files
committed
refactor: use GoRouter with refreshListenable
- Router creation moved to createRouter - AppBloc stream used for refreshListenable - Improved redirect logic with debug prints
1 parent 8730da9 commit 716a7c0

File tree

6 files changed

+273
-127
lines changed

6 files changed

+273
-127
lines changed

lib/app/view/app.dart

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,99 @@
1+
// Required for StreamSubscription
2+
13
import 'package:flex_color_scheme/flex_color_scheme.dart';
4+
// Required for Listenable
25
import 'package:flutter/material.dart';
36
import 'package:flutter_bloc/flutter_bloc.dart';
7+
import 'package:go_router/go_router.dart'; // Import GoRouter
48
import 'package:google_fonts/google_fonts.dart';
59
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
610
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
711
import 'package:ht_main/app/bloc/app_bloc.dart';
812
import 'package:ht_main/l10n/l10n.dart';
13+
// Import the createRouter function and the helper stream
14+
import 'package:ht_main/router/go_router_refresh_stream.dart';
915
import 'package:ht_main/router/router.dart';
16+
// Routes class is still needed if createRouter uses it, which it does
1017

1118
class App extends StatelessWidget {
1219
const App({
1320
required HtHeadlinesRepository htHeadlinesRepository,
1421
required HtAuthenticationRepository htAuthenticationRepository,
22+
// AppBloc is no longer passed in constructor
1523
super.key,
1624
}) : _htHeadlinesRepository = htHeadlinesRepository,
1725
_htAuthenticationRepository = htAuthenticationRepository;
1826

1927
final HtHeadlinesRepository _htHeadlinesRepository;
2028
final HtAuthenticationRepository _htAuthenticationRepository;
29+
// No longer storing AppBloc instance here
2130

2231
@override
2332
Widget build(BuildContext context) {
33+
// Provide repositories globally
2434
return MultiRepositoryProvider(
2535
providers: [
2636
RepositoryProvider.value(value: _htHeadlinesRepository),
2737
RepositoryProvider.value(value: _htAuthenticationRepository),
2838
],
29-
child: MultiBlocProvider(
30-
providers: [
31-
BlocProvider(
32-
create: (context) => AppBloc(
33-
authenticationRepository:
34-
context.read<HtAuthenticationRepository>(),
35-
),
36-
),
37-
],
39+
// Create AppBloc here using BlocProvider
40+
child: BlocProvider(
41+
create: (context) => AppBloc(
42+
authenticationRepository: context.read<HtAuthenticationRepository>(),
43+
),
44+
// _AppView now reads AppBloc from context
3845
child: const _AppView(),
3946
),
4047
);
4148
}
4249
}
4350

44-
class _AppView extends StatelessWidget {
45-
const _AppView();
51+
// Change _AppView to StatefulWidget to manage GoRouter lifecycle
52+
class _AppView extends StatefulWidget {
53+
const _AppView(); // No longer needs appBloc passed
54+
55+
@override
56+
State<_AppView> createState() => _AppViewState();
57+
}
58+
59+
class _AppViewState extends State<_AppView> {
60+
// Store the router and the refresh stream listener
61+
late final GoRouter _router;
62+
late final GoRouterRefreshStream _refreshListener;
63+
64+
@override
65+
void initState() {
66+
super.initState();
67+
// Get the AppBloc instance from the BlocProvider above
68+
final appBloc = context.read<AppBloc>();
69+
// Create the refresh listener using the AppBloc stream
70+
_refreshListener = GoRouterRefreshStream(appBloc.stream);
71+
// Create the router instance by calling the function from router.dart
72+
_router = createRouter(refreshListenable: _refreshListener);
73+
}
74+
75+
@override
76+
void dispose() {
77+
// Dispose the refresh listener when the widget is disposed
78+
_refreshListener.dispose();
79+
super.dispose();
80+
}
4681

4782
@override
4883
Widget build(BuildContext context) {
84+
// Use BlocBuilder to react to theme changes from AppBloc
4985
return BlocBuilder<AppBloc, AppState>(
86+
// Use buildWhen for optimization if only theme affects MaterialApp
87+
buildWhen: (previous, current) => previous.themeMode != current.themeMode,
5088
builder: (context, state) {
5189
return MaterialApp.router(
5290
debugShowCheckedModeBanner: false,
53-
theme:
54-
state.themeMode == ThemeMode.light ? lightTheme() : darkTheme(),
55-
routerConfig: appRouter,
91+
// Apply theme based on AppBloc state
92+
themeMode: state.themeMode,
93+
theme: lightTheme(),
94+
darkTheme: darkTheme(),
95+
// Use the router created and stored in the state
96+
routerConfig: _router,
5697
localizationsDelegates: AppLocalizations.localizationsDelegates,
5798
supportedLocales: AppLocalizations.supportedLocales,
5899
);
@@ -61,16 +102,17 @@ class _AppView extends StatelessWidget {
61102
}
62103
}
63104

105+
// --- Themes (unchanged) ---
64106
ThemeData lightTheme() {
65107
return FlexThemeData.light(
66-
scheme: FlexScheme.material,
108+
scheme: FlexScheme.greyLaw,
67109
fontFamily: GoogleFonts.notoSans().fontFamily,
68110
);
69111
}
70112

71113
ThemeData darkTheme() {
72114
return FlexThemeData.dark(
73-
scheme: FlexScheme.material,
115+
scheme: FlexScheme.greyLaw,
74116
fontFamily: GoogleFonts.notoSans().fontFamily,
75117
);
76118
}

lib/authentication/view/authentication_page.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,19 @@ class AuthenticationPage extends StatelessWidget {
1212
create: (context) => AuthenticationBloc(
1313
authenticationRepository: context.read<HtAuthenticationRepository>(),
1414
),
15-
child: _AuthenticationView(),
15+
child: _AuthenticationView(),
1616
);
1717
}
1818
}
1919

2020
class _AuthenticationView extends StatelessWidget {
2121
_AuthenticationView();
2222

23-
final _emailController = TextEditingController();
24-
final _passwordController = TextEditingController();
23+
final _emailController = TextEditingController();
24+
final _passwordController = TextEditingController();
2525

2626
@override
2727
Widget build(BuildContext context) {
28-
2928
return Scaffold(
3029
body: SafeArea(
3130
child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
@@ -48,7 +47,8 @@ class _AuthenticationView extends StatelessWidget {
4847
children: [
4948
const Text(
5049
'Sign In',
51-
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
50+
style:
51+
TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
5252
),
5353
const SizedBox(height: 32),
5454
TextFormField(
@@ -96,9 +96,9 @@ class _AuthenticationView extends StatelessWidget {
9696
const SizedBox(height: 16),
9797
ElevatedButton(
9898
onPressed: () {
99-
context
100-
.read<AuthenticationBloc>()
101-
.add(const AuthenticationAnonymousSignInRequested());
99+
context.read<AuthenticationBloc>().add(
100+
const AuthenticationAnonymousSignInRequested(),
101+
);
102102
},
103103
child: const Text('Sign In Anonymously'),
104104
),

lib/main.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,34 @@ import 'package:ht_authentication_repository/ht_authentication_repository.dart';
77
import 'package:ht_headlines_firestore/ht_headlines_firestore.dart';
88
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
99
import 'package:ht_main/app/app.dart';
10+
// No longer importing AppBloc here
1011
import 'package:ht_main/bloc_observer.dart';
1112
import 'package:ht_main/firebase_options.dart';
1213

1314
void main() async {
1415
WidgetsFlutterBinding.ensureInitialized();
1516
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
1617
Bloc.observer = const AppBlocObserver();
17-
final firestore = FirebaseFirestore.instance;
18-
final headlinesClient = HtHeadlinesFirestore(firestore: firestore);
19-
final headlinesRepository = HtHeadlinesRepository(client: headlinesClient);
18+
19+
// --- Instantiate Repositories ---
20+
// 1. Authentication Repository
2021
final authenticationClient = HtAuthenticationFirebase();
2122
final authenticationRepository = HtAuthenticationRepository(
2223
authenticationClient: authenticationClient,
2324
);
25+
26+
// 2. Headlines Repository
27+
final firestore = FirebaseFirestore.instance;
28+
final headlinesClient = HtHeadlinesFirestore(firestore: firestore);
29+
final headlinesRepository = HtHeadlinesRepository(client: headlinesClient);
30+
// --- End Instantiation ---
31+
2432
runApp(
33+
// Pass only repositories to the App widget
2534
App(
2635
htAuthenticationRepository: authenticationRepository,
2736
htHeadlinesRepository: headlinesRepository,
37+
// AppBloc instance is no longer created or passed here
2838
),
2939
);
3040
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'dart:async';
2+
import 'package:flutter/foundation.dart';
3+
4+
/// Helper class to convert a Stream to a Listenable for GoRouter.
5+
///
6+
/// Every time the "stream" receives an event, this [ChangeNotifier] notifies
7+
/// its listeners, causing GoRouter to re-evaluate its routes and redirects
8+
/// when used with `refreshListenable`.
9+
class GoRouterRefreshStream extends ChangeNotifier {
10+
/// Creates a [GoRouterRefreshStream].
11+
GoRouterRefreshStream(Stream<dynamic> stream) {
12+
// Notify listeners immediately to ensure initial state is considered.
13+
// Although GoRouter likely handles initial state, this ensures consistency.
14+
notifyListeners();
15+
// Subscribe to the stream. Use asBroadcastStream to allow multiple
16+
// listeners if needed elsewhere, though typically only GoRouter
17+
// listens here.
18+
_subscription = stream.asBroadcastStream().listen(
19+
(dynamic _) => notifyListeners(), // Notify on every stream event
20+
);
21+
}
22+
23+
late final StreamSubscription<dynamic> _subscription;
24+
25+
/// Cancels the stream subscription when this notifier is disposed.
26+
@override
27+
void dispose() {
28+
_subscription.cancel();
29+
super.dispose();
30+
}
31+
}

0 commit comments

Comments
 (0)