beamer
beamer copied to clipboard
Bug(?): Custom location in another custom location combinated with bottom navigation
Describe the bug
Hello, thanks @slovnicki for offering the package.
After a couple of time, I got an issue using beamer in combination with the bottom navigation.
I added an example code (modified from bottom_navigation_multiple_beamers
that contains the following:
- it has two tabs ("books" and "articles")
- in the
BooksLocation
, I added another location (is that even allowed?!) - I navigate to that location by tapping on the first book ListTile
- Usually I would expect the back button on the AppBar
This issue is kinda annoying because I would like to use a
BookDetailsLocation
instead of just copying the pages ofBookDetailsLocation
toBooksLocation
. E. g.BookDetailsLocation
could also overridebuilder
with some custom widgets only for that location. So do you have a good idea how to solve my issue? Thank you very much!
Beamer version: v1.5.3
To Reproduce Steps to reproduce the behavior:
- Copy paste the example code that I changed from
bottom_navigation_multiple_beamers
- Start this e. g. on web.
- Click in Tab "Books" on the first book
- It navigates to a custom location
- The back button in the AppBar should be visible but its not
Expected behavior When using a location inside another location, I expect to navigate forth and back.
Additional context
The example code, you can try in your main.dart
:
import 'package:flutter/material.dart';
import 'package:beamer/beamer.dart';
// DATA
const List<Map<String, String>> books = [
{
'id': '1',
'title': 'Stranger in a Strange Land',
'author': 'Robert A. Heinlein',
},
{
'id': '2',
'title': 'Foundation',
'author': 'Isaac Asimov',
},
{
'id': '3',
'title': 'Fahrenheit 451',
'author': 'Ray Bradbury',
},
];
const List<Map<String, String>> articles = [
{
'id': '1',
'title': 'Explaining Flutter Nav 2.0 and Beamer',
'author': 'Toby Lewis',
},
{
'id': '2',
'title': 'Flutter Navigator 2.0 for mobile dev: 101',
'author': 'Lulupointu',
},
{
'id': '3',
'title': 'Flutter: An Easy and Pragmatic Approach to Navigator 2.0',
'author': 'Marco Muccinelli',
},
];
// SCREENS
class BooksScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Books'),
),
body: ListView(
children: books
.map(
(book) => ListTile(
title: Text(book['title']!),
subtitle: Text(book['author']!),
onTap: () => context.beamToNamed('/books/detail'),
),
)
.toList(),
),
);
}
}
class BookDetailsScreen extends StatelessWidget {
const BookDetailsScreen({required this.book});
final Map<String, String> book;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book['title']!),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Author: ${book['author']}'),
),
);
}
}
class ArticlesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Articles')),
body: ListView(
children: articles
.map(
(article) => ListTile(
title: Text(article['title']!),
subtitle: Text(article['author']!),
onTap: () => context.beamToNamed('/articles/${article['id']}'),
),
)
.toList(),
),
);
}
}
class ArticleDetailsScreen extends StatelessWidget {
const ArticleDetailsScreen({required this.article});
final Map<String, String> article;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(article['title']!),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Author: ${article['author']}'),
),
);
}
}
// LOCATIONS
class BooksLocation extends BeamLocation<BeamState> {
BooksLocation(RouteInformation routeInformation) : super(routeInformation);
@override
List<String> get pathPatterns => ['/books/detail'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) => [
BeamPage(
key: ValueKey('books'),
title: 'Books',
type: BeamPageType.noTransition,
child: BooksScreen(),
),
if (state.pathPatternSegments.contains('detail'))
BeamPage(
key: ValueKey('book-details-location'),
title: 'Book-Details-Location',
child: Beamer(
routerDelegate: BeamerDelegate(
locationBuilder: (routeInformation, __) => BooksDetailLocation(
routeInformation,
),
),
),
),
];
}
class BooksDetailLocation extends BeamLocation<BeamState> {
BooksDetailLocation(RouteInformation routeInformation)
: super(routeInformation);
@override
List<String> get pathPatterns => ['/books/detail'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) => [
BeamPage(
key: ValueKey('books-detail'),
title: 'Books-Detail',
type: BeamPageType.noTransition,
child: BookDetailsScreen(book: books[0]),
),
];
}
class ArticlesLocation extends BeamLocation<BeamState> {
ArticlesLocation(RouteInformation routeInformation) : super(routeInformation);
@override
List<String> get pathPatterns => ['/articles/:articleId'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) => [
BeamPage(
key: ValueKey('articles'),
title: 'Articles',
type: BeamPageType.noTransition,
child: ArticlesScreen(),
),
if (state.pathParameters.containsKey('articleId'))
BeamPage(
key: ValueKey('articles-${state.pathParameters['articleId']}'),
title: articles.firstWhere((article) =>
article['id'] == state.pathParameters['articleId'])['title'],
child: ArticleDetailsScreen(
article: articles.firstWhere((article) =>
article['id'] == state.pathParameters['articleId']),
),
),
];
}
// APP
class AppScreen extends StatefulWidget {
@override
_AppScreenState createState() => _AppScreenState();
}
class _AppScreenState extends State<AppScreen> {
late int currentIndex;
final routerDelegates = [
BeamerDelegate(
initialPath: '/books',
locationBuilder: (routeInformation, _) {
if (routeInformation.location!.contains('books')) {
return BooksLocation(routeInformation);
}
return NotFound(path: routeInformation.location!);
},
),
BeamerDelegate(
initialPath: '/articles',
locationBuilder: (routeInformation, _) {
if (routeInformation.location!.contains('articles')) {
return ArticlesLocation(routeInformation);
}
return NotFound(path: routeInformation.location!);
},
),
];
@override
void didChangeDependencies() {
super.didChangeDependencies();
final uriString = Beamer.of(context).configuration.location!;
currentIndex = uriString.contains('books') ? 0 : 1;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: currentIndex,
children: [
Beamer(
routerDelegate: routerDelegates[0],
),
Container(
color: Colors.blueAccent,
padding: const EdgeInsets.all(32.0),
child: Beamer(
routerDelegate: routerDelegates[1],
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
items: [
BottomNavigationBarItem(label: 'Books', icon: Icon(Icons.book)),
BottomNavigationBarItem(label: 'Articles', icon: Icon(Icons.article)),
],
onTap: (index) {
if (index != currentIndex) {
setState(() => currentIndex = index);
routerDelegates[currentIndex].update(rebuild: false);
}
},
),
);
}
}
class MyApp extends StatelessWidget {
final routerDelegate = BeamerDelegate(
initialPath: '/books',
locationBuilder: RoutesLocationBuilder(
routes: {
'*': (context, state, data) => AppScreen(),
},
),
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerDelegate: routerDelegate,
routeInformationParser: BeamerParser(),
backButtonDispatcher: BeamerBackButtonDispatcher(
delegate: routerDelegate,
),
);
}
}
void main() => runApp(MyApp());