Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add go router package and routing to the app #1401

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flutter_news_example/api/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,4 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.4.0 <4.0.0"
dart: ">=3.5.0 <4.0.0"
136 changes: 123 additions & 13 deletions flutter_news_example/lib/app/routes/routes.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,127 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_news_example/app/app.dart';
import 'package:flutter_news_example/article/article.dart';
import 'package:flutter_news_example/home/home.dart';
import 'package:flutter_news_example/login/login.dart';
import 'package:flutter_news_example/magic_link_prompt/view/magic_link_prompt_page.dart';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to be able to use barrel files instead of importing the file directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check other imports too

import 'package:flutter_news_example/network_error/network_error.dart';
import 'package:flutter_news_example/notification_preferences/notification_preferences.dart';
import 'package:flutter_news_example/onboarding/onboarding.dart';
import 'package:flutter_news_example/slideshow/slideshow.dart';
import 'package:flutter_news_example/subscriptions/view/manage_subscription_page.dart';
import 'package:flutter_news_example/user_profile/user_profile.dart';
import 'package:go_router/go_router.dart';
import 'package:news_blocks/news_blocks.dart';

List<Page<dynamic>> onGenerateAppViewPages(
AppStatus state,
List<Page<dynamic>> pages,
) {
switch (state) {
case AppStatus.onboardingRequired:
return [OnboardingPage.page()];
case AppStatus.unauthenticated:
case AppStatus.authenticated:
return [HomePage.page()];
}
}
final GoRouter router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: HomePage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
},
routes: <RouteBase>[
GoRoute(
name: NetworkErrorPage.routePath,
path: NetworkErrorPage.routePath,
builder: (BuildContext context, GoRouterState state) {
final onRetry = state.extra as VoidCallback?;
return NetworkError(onRetry: onRetry);
},
),
GoRoute(
name: LoginWithEmailPage.routePath,
path: LoginWithEmailPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const LoginWithEmailPage();
},
routes: <RouteBase>[
GoRoute(
name: MagicLinkPromptPage.routePath,
path: MagicLinkPromptPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return MagicLinkPromptPage(
email: state.uri.queryParameters['email']!,
);
},
),
],
),
GoRoute(
name: ArticlePage.routeName,
path: ArticlePage.routePath,
builder: (BuildContext context, GoRouterState state) {
final id = state.pathParameters['id'];

final isVideoArticle = bool.tryParse(
state.uri.queryParameters['isVideoArticle'] ?? 'false',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state.uri.queryParameters can be abstracted as a variable to reuse

) ??
false;
final interstitialAdBehavior =
state.uri.queryParameters['interstitialAdBehavior'] != null
? InterstitialAdBehavior.values.firstWhere(
(e) =>
e.toString() ==
'InterstitialAdBehavior.'
// ignore: lines_longer_than_80_chars
'${state.uri.queryParameters['interstitialAdBehavior']}',
)
: null;

if (id == null) {
throw Exception('Missing required "id" parameter');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's shown when this happens ? Isn't better to render NetworkError instead?

}

return ArticlePage(
id: id,
isVideoArticle: isVideoArticle,
interstitialAdBehavior:
interstitialAdBehavior ?? InterstitialAdBehavior.onOpen,
);
},
routes: <RouteBase>[
GoRoute(
name: SlideshowPage.routePath,
path: SlideshowPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return SlideshowPage(
slideshow: state.extra! as SlideshowBlock,
articleId: state.pathParameters['id']!,
);
},
),
],
),
GoRoute(
name: UserProfilePage.routePath,
path: UserProfilePage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const UserProfilePage();
},
routes: <RouteBase>[
GoRoute(
name: ManageSubscriptionPage.routePath,
path: ManageSubscriptionPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const ManageSubscriptionPage();
},
),
GoRoute(
name: NotificationPreferencesPage.routePath,
path: NotificationPreferencesPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const NotificationPreferencesPage();
},
),
],
),
],
),
GoRoute(
name: OnboardingPage.routePath,
path: OnboardingPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const OnboardingPage();
},
),
],
);
15 changes: 7 additions & 8 deletions flutter_news_example/lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:ads_consent_client/ads_consent_client.dart';
import 'package:analytics_repository/analytics_repository.dart';
import 'package:app_ui/app_ui.dart';
import 'package:article_repository/article_repository.dart';
import 'package:flow_builder/flow_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/ads/ads.dart';
Expand Down Expand Up @@ -112,18 +111,18 @@ class AppView extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MaterialApp(
return MaterialApp.router(
routerConfig: router,
themeMode: ThemeMode.light,
theme: const AppTheme().themeData,
darkTheme: const AppDarkTheme().themeData,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: AuthenticatedUserListener(
child: FlowBuilder<AppStatus>(
state: context.select((AppBloc bloc) => bloc.state.status),
onGeneratePages: onGenerateAppViewPages,
),
),
builder: (context, router) {
return AuthenticatedUserListener(
child: router ?? const SizedBox(),
);
},
);
}
}
17 changes: 3 additions & 14 deletions flutter_news_example/lib/article/view/article_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class ArticlePage extends StatelessWidget {
super.key,
});

static const routeName = 'article';
static const routePath = 'article/:id';

/// The id of the requested article.
final String id;

Expand All @@ -38,20 +41,6 @@ class ArticlePage extends StatelessWidget {
/// Default to [InterstitialAdBehavior.onOpen]
final InterstitialAdBehavior interstitialAdBehavior;

static Route<void> route({
required String id,
bool isVideoArticle = false,
InterstitialAdBehavior interstitialAdBehavior =
InterstitialAdBehavior.onOpen,
}) =>
MaterialPageRoute<void>(
builder: (_) => ArticlePage(
id: id,
isVideoArticle: isVideoArticle,
interstitialAdBehavior: interstitialAdBehavior,
),
);

@override
Widget build(BuildContext context) {
return BlocProvider<ArticleBloc>(
Expand Down
14 changes: 7 additions & 7 deletions flutter_news_example/lib/article/widgets/article_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_news_example/categories/categories.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/network_error/network_error.dart';
import 'package:flutter_news_example_api/client.dart';
import 'package:go_router/go_router.dart';
import 'package:visibility_detector/visibility_detector.dart';

class ArticleContent extends StatelessWidget {
Expand Down Expand Up @@ -36,13 +37,12 @@ class ArticleContent extends StatelessWidget {
child: BlocListener<ArticleBloc, ArticleState>(
listener: (context, state) {
if (state.status == ArticleStatus.failure && state.content.isEmpty) {
Navigator.of(context).push<void>(
NetworkError.route(
onRetry: () {
context.read<ArticleBloc>().add(const ArticleRequested());
Navigator.of(context).pop();
},
),
context.goNamed(
NetworkErrorPage.routePath,
extra: () {
context.read<ArticleBloc>().add(const ArticleRequested());
Navigator.of(context).pop();
},
);
} else if (state.status == ArticleStatus.shareFailure) {
_handleShareFailure(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_news_example/categories/categories.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/newsletter/newsletter.dart';
import 'package:flutter_news_example/slideshow/slideshow.dart';
import 'package:go_router/go_router.dart';
import 'package:news_blocks/news_blocks.dart';
import 'package:news_blocks_ui/news_blocks_ui.dart';

Expand Down Expand Up @@ -100,11 +101,10 @@ class ArticleContentItem extends StatelessWidget {
BlockAction action,
) async {
if (action is NavigateToSlideshowAction) {
await Navigator.of(context).push<void>(
SlideshowPage.route(
slideshow: action.slideshow,
articleId: action.articleId,
),
context.goNamed(
SlideshowPage.routePath,
pathParameters: {'id': action.articleId},
extra: action.slideshow,
);
}
}
Expand Down
16 changes: 7 additions & 9 deletions flutter_news_example/lib/feed/widgets/category_feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/feed/feed.dart';
import 'package:flutter_news_example/network_error/network_error.dart';
import 'package:flutter_news_example_api/client.dart';
import 'package:go_router/go_router.dart';

class CategoryFeed extends StatelessWidget {
const CategoryFeed({
Expand Down Expand Up @@ -31,15 +32,12 @@ class CategoryFeed extends StatelessWidget {
return BlocListener<FeedBloc, FeedState>(
listener: (context, state) {
if (state.status == FeedStatus.failure && state.feed.isEmpty) {
Navigator.of(context).push<void>(
NetworkError.route(
onRetry: () {
context
.read<FeedBloc>()
.add(FeedRefreshRequested(category: category));
Navigator.of(context).pop();
},
),
context.goNamed(
NetworkErrorPage.routePath,
extra: () {
context.read<FeedBloc>().add(FeedRequested(category: category));
Navigator.of(context).pop();
},
);
}
},
Expand Down
15 changes: 11 additions & 4 deletions flutter_news_example/lib/feed/widgets/category_feed_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_news_example/article/article.dart';
import 'package:flutter_news_example/categories/categories.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/newsletter/newsletter.dart';
import 'package:go_router/go_router.dart';
import 'package:news_blocks/news_blocks.dart';
import 'package:news_blocks_ui/news_blocks_ui.dart';

Expand Down Expand Up @@ -88,12 +89,18 @@ class CategoryFeedItem extends StatelessWidget {
BlockAction action,
) async {
if (action is NavigateToArticleAction) {
await Navigator.of(context).push<void>(
ArticlePage.route(id: action.articleId),
context.goNamed(
ArticlePage.routeName,
pathParameters: {'id': action.articleId},
);
} else if (action is NavigateToVideoArticleAction) {
await Navigator.of(context).push<void>(
ArticlePage.route(id: action.articleId, isVideoArticle: true),
context.goNamed(
ArticlePage.routeName,
pathParameters: {'id': action.articleId},
queryParameters: {
'articleId': action.articleId,
'isVideoArticle': true,
},
);
} else if (action is NavigateToFeedCategoryAction) {
context
Expand Down
2 changes: 2 additions & 0 deletions flutter_news_example/lib/home/view/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'package:news_repository/news_repository.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});

static const routePath = '/';

static Page<void> page() => const MaterialPage<void>(child: HomePage());

@override
Expand Down
13 changes: 13 additions & 0 deletions flutter_news_example/lib/home/view/home_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import 'package:flutter_news_example/feed/feed.dart';
import 'package:flutter_news_example/home/home.dart';
import 'package:flutter_news_example/login/login.dart';
import 'package:flutter_news_example/navigation/navigation.dart';
import 'package:flutter_news_example/onboarding/view/onboarding_page.dart';
import 'package:flutter_news_example/search/search.dart';
import 'package:flutter_news_example/user_profile/user_profile.dart';
import 'package:go_router/go_router.dart';

class HomeView extends StatelessWidget {
const HomeView({super.key});
Expand All @@ -31,6 +33,17 @@ class HomeView extends StatelessWidget {
}
},
),
BlocListener<AppBloc, AppState>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlocListener<CardActivationCubit, CardActivationState>(
  listenWhen: (_, current) => current  == AppStatus.onboardingRequired , 
  listener: (context, _) => context.goNamed(OnboardingPage.routePath),
),

Something like this could be simpler, what do you think?

listener: (context, state) {
switch (state.status) {
case AppStatus.onboardingRequired:
context.goNamed(OnboardingPage.routePath);
case AppStatus.unauthenticated:
case AppStatus.authenticated:
return;
}
},
),
BlocListener<HomeCubit, HomeState>(
listener: (context, state) {
FocusManager.instance.primaryFocus?.unfocus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'package:user_repository/user_repository.dart';
class LoginWithEmailPage extends StatelessWidget {
const LoginWithEmailPage({super.key});

static const routePath = 'login-with-email';

static Route<void> route() =>
MaterialPageRoute<void>(builder: (_) => const LoginWithEmailPage());

Expand Down
5 changes: 2 additions & 3 deletions flutter_news_example/lib/login/widgets/login_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_news_example/app/app.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/login/login.dart';
import 'package:form_inputs/form_inputs.dart';
import 'package:go_router/go_router.dart';

class LoginForm extends StatelessWidget {
const LoginForm({super.key});
Expand Down Expand Up @@ -201,9 +202,7 @@ class _ContinueWithEmailLoginButton extends StatelessWidget {
Widget build(BuildContext context) {
return AppButton.outlinedTransparentDarkAqua(
key: const Key('loginForm_emailLogin_appButton'),
onPressed: () => Navigator.of(context).push<void>(
LoginWithEmailPage.route(),
),
onPressed: () => context.goNamed(LoginWithEmailPage.routePath),
textStyle: Theme.of(context).textTheme.titleMedium,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down
Loading
Loading