Skip to content

Commit

Permalink
fix: build routes refactor and tests added
Browse files Browse the repository at this point in the history
  • Loading branch information
SofiaRey committed Jan 8, 2025
1 parent 5e44123 commit 5edd77a
Show file tree
Hide file tree
Showing 26 changed files with 385 additions and 146 deletions.
74 changes: 10 additions & 64 deletions flutter_news_example/lib/app/routes/routes.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:flutter/widgets.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';
Expand All @@ -10,107 +9,56 @@ 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';

final GoRouter router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: HomePage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const HomePage();
},
builder: HomePage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: NetworkErrorPage.routePath,
path: NetworkErrorPage.routePath,
builder: (BuildContext context, GoRouterState state) {
final onRetry = state.extra as VoidCallback?;
return NetworkError(onRetry: onRetry);
},
builder: NetworkErrorPage.routeBuilder,
),
GoRoute(
name: LoginWithEmailPage.routePath,
path: LoginWithEmailPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const LoginWithEmailPage();
},
builder: LoginWithEmailPage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: MagicLinkPromptPage.routePath,
path: MagicLinkPromptPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return MagicLinkPromptPage(
email: state.uri.queryParameters['email']!,
);
},
builder: MagicLinkPromptPage.routeBuilder,
),
],
),
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',
) ??
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,
);
},
builder: ArticlePage.routeBuilder,
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']!,
);
},
builder: SlideshowPage.routeBuilder,
),
],
),
GoRoute(
name: UserProfilePage.routePath,
path: UserProfilePage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const UserProfilePage();
},
builder: UserProfilePage.routeBuilder,
routes: <RouteBase>[
GoRoute(
name: ManageSubscriptionPage.routePath,
path: ManageSubscriptionPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const ManageSubscriptionPage();
},
builder: ManageSubscriptionPage.routeBuilder,
),
GoRoute(
name: NotificationPreferencesPage.routePath,
path: NotificationPreferencesPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const NotificationPreferencesPage();
},
builder: NotificationPreferencesPage.routeBuilder,
),
],
),
Expand All @@ -119,9 +67,7 @@ final GoRouter router = GoRouter(
GoRoute(
name: OnboardingPage.routePath,
path: OnboardingPage.routePath,
builder: (BuildContext context, GoRouterState state) {
return const OnboardingPage();
},
builder: OnboardingPage.routeBuilder,
),
],
);
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
34 changes: 34 additions & 0 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 @@ -31,6 +32,39 @@ class ArticlePage extends StatelessWidget {
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 Down
11 changes: 5 additions & 6 deletions flutter_news_example/lib/article/widgets/article_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +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) {
context.goNamed(
await context.pushNamed(
NetworkErrorPage.routePath,
extra: () {
context.read<ArticleBloc>().add(const ArticleRequested());
Navigator.of(context).pop();
},
);
if (context.mounted) {
context.read<ArticleBloc>().add(const ArticleRequested());
}
} else if (state.status == ArticleStatus.shareFailure) {
_handleShareFailure(context);
}
Expand Down
12 changes: 6 additions & 6 deletions flutter_news_example/lib/feed/widgets/category_feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +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) {
context.goNamed(
await context.pushNamed(
NetworkErrorPage.routePath,
extra: () {
context.read<FeedBloc>().add(FeedRequested(category: category));
Navigator.of(context).pop();
},
);
// TODO: check if this implementation works (tests)
if (context.mounted) {
context.read<FeedBloc>().add(FeedRequested(category: category));
}
}
},
child: RefreshIndicator(
Expand Down
7 changes: 6 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,14 +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 const routePath = '/';

static Page<void> page() => const MaterialPage<void>(child: HomePage());
static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) =>
const HomePage();

@override
Widget build(BuildContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import 'package:app_ui/app_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/login/login.dart';
import 'package:go_router/go_router.dart';
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());
static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) =>
const LoginWithEmailPage();

@override
Widget build(BuildContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:app_ui/app_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_example/login/login.dart';
import 'package:flutter_news_example/magic_link_prompt/magic_link_prompt.dart';
import 'package:go_router/go_router.dart';

class MagicLinkPromptPage extends StatelessWidget {
const MagicLinkPromptPage({required this.email, super.key});
Expand All @@ -10,6 +11,14 @@ class MagicLinkPromptPage extends StatelessWidget {

final String email;

static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) {
final email = state.uri.queryParameters['email']!;
return MagicLinkPromptPage(email: email);
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down
20 changes: 13 additions & 7 deletions flutter_news_example/lib/network_error/view/network_error.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import 'package:app_ui/app_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:go_router/go_router.dart';

/// {@template network_error}
/// A network error alert page.
/// {@endtemplate}
class NetworkErrorPage extends StatelessWidget {
/// {@macro network_error}
const NetworkErrorPage({super.key, this.onRetry});

/// An optional callback which is invoked when the retry button is pressed.
final VoidCallback? onRetry;
const NetworkErrorPage({
super.key,
});

static const routePath = 'network-error';

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

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(leading: const AppBackButton()),
body: Center(
child: NetworkError(onRetry: onRetry),
body: const Center(
child: NetworkError(),
),
);
}
Expand Down Expand Up @@ -59,7 +65,7 @@ class NetworkError extends StatelessWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxxlg),
child: AppButton.darkAqua(
onPressed: onRetry,
onPressed: onRetry ?? context.pop,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/l10n/l10n.dart';
import 'package:flutter_news_example/notification_preferences/notification_preferences.dart';
import 'package:go_router/go_router.dart';
import 'package:news_repository/news_repository.dart';
import 'package:notifications_repository/notifications_repository.dart';

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

static const routePath = 'notification-preferences';
static MaterialPageRoute<void> route() {
return MaterialPageRoute(
builder: (_) => const NotificationPreferencesPage(),
);
}

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

@override
Widget build(BuildContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import 'package:app_ui/app_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_example/onboarding/onboarding.dart';
import 'package:go_router/go_router.dart';
import 'package:notifications_repository/notifications_repository.dart';

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

static const routePath = '/onboarding';

static Page<void> page() => const MaterialPage<void>(child: OnboardingPage());
static Widget routeBuilder(
BuildContext context,
GoRouterState state,
) =>
const OnboardingPage();

@override
Widget build(BuildContext context) {
Expand Down
Loading

0 comments on commit 5edd77a

Please sign in to comment.