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 all 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"
84 changes: 70 additions & 14 deletions flutter_news_example/lib/app/routes/routes.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
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';
import 'package:flutter_news_example/network_error/network_error.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/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';

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: HomePage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: NetworkErrorPage.routePath,
path: NetworkErrorPage.routePath,
builder: NetworkErrorPage.routeBuilder,
),
GoRoute(
name: LoginWithEmailPage.routePath,
path: LoginWithEmailPage.routePath,
builder: LoginWithEmailPage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: MagicLinkPromptPage.routePath,
path: MagicLinkPromptPage.routePath,
builder: MagicLinkPromptPage.routeBuilder,
),
],
),
GoRoute(
name: ArticlePage.routeName,
path: ArticlePage.routePath,
builder: ArticlePage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: SlideshowPage.routePath,
path: SlideshowPage.routePath,
builder: SlideshowPage.routeBuilder,
),
],
),
GoRoute(
name: UserProfilePage.routePath,
path: UserProfilePage.routePath,
builder: UserProfilePage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: ManageSubscriptionPage.routePath,
path: ManageSubscriptionPage.routePath,
builder: ManageSubscriptionPage.routeBuilder,
),
GoRoute(
name: NotificationPreferencesPage.routePath,
path: NotificationPreferencesPage.routePath,
builder: NotificationPreferencesPage.routeBuilder,
),
],
),
],
),
GoRoute(
name: OnboardingPage.routePath,
path: OnboardingPage.routePath,
builder: OnboardingPage.routeBuilder,
),
],
);
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(),
);
},
);
}
}
2 changes: 1 addition & 1 deletion flutter_news_example/lib/article/bloc/article_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:news_blocks/news_blocks.dart';
import 'package:share_launcher/share_launcher.dart';

part 'article_bloc.g.dart';
part 'article_event.dart';
part 'article_state.dart';
part 'article_bloc.g.dart';

class ArticleBloc extends HydratedBloc<ArticleEvent, ArticleState> {
ArticleBloc({
Expand Down
51 changes: 37 additions & 14 deletions flutter_news_example/lib/article/view/article_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_news_example/app/app.dart';
import 'package:flutter_news_example/article/article.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/subscriptions/subscriptions.dart';
import 'package:go_router/go_router.dart';
import 'package:news_blocks_ui/news_blocks_ui.dart';
import 'package:share_launcher/share_launcher.dart';

Expand All @@ -28,6 +29,42 @@ class ArticlePage extends StatelessWidget {
super.key,
});

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

static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) {
final id = state.pathParameters['id'];

final isVideoArticle = bool.tryParse(
state.uri.queryParameters['isVideoArticle'] ?? 'false',
) ??
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');
}

return ArticlePage(
id: id,
isVideoArticle: isVideoArticle,
interstitialAdBehavior:
interstitialAdBehavior ?? InterstitialAdBehavior.onOpen,
);
}

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

Expand All @@ -38,20 +75,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
15 changes: 7 additions & 8 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 All @@ -34,16 +35,14 @@ class ArticleContent extends StatelessWidget {

return ArticleContentSeenListener(
child: BlocListener<ArticleBloc, ArticleState>(
listener: (context, state) {
listener: (context, state) async {
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();
},
),
await context.pushNamed(
NetworkErrorPage.routePath,
);
if (context.mounted) {
context.read<ArticleBloc>().add(const ArticleRequested());
}
} 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
18 changes: 8 additions & 10 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 All @@ -29,18 +30,15 @@ class CategoryFeed extends StatelessWidget {
.select((FeedBloc bloc) => bloc.state.status == FeedStatus.failure);

return BlocListener<FeedBloc, FeedState>(
listener: (context, state) {
listener: (context, state) async {
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();
},
),
await context.pushNamed(
NetworkErrorPage.routePath,
);
// TODO: check if this implementation works (tests)
if (context.mounted) {
context.read<FeedBloc>().add(FeedRequested(category: category));
}
}
},
child: RefreshIndicator(
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
9 changes: 8 additions & 1 deletion flutter_news_example/lib/home/view/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/feed/feed.dart';
import 'package:flutter_news_example/home/home.dart';
import 'package:go_router/go_router.dart';
import 'package:news_repository/news_repository.dart';

class HomePage extends StatelessWidget {
const HomePage({super.key});

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

static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) =>
const HomePage();

@override
Widget build(BuildContext context) {
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
Loading
Loading