英文:
Flutter Strange behavior when pressing the Android back button on child Beamer
问题
在我的应用程序中,一开始我有3个具有以下路由的页面:
routerDelegate = BeamerDelegate(
initialPath: initialPath,
locationBuilder: RoutesLocationBuilder(
routes: {
'/signup': (_, __, ___) => const SignupScreenWidget(),
'/verifyCode': (_, __, ___) => const VerifyCodeRootScreenWidget(),
'/home': (_, __, ___) => const HomeScreen(),
},
),
);
在主页内部,我定义了以下的 BeamLocations
。由于在 ChefRootWidget
内部有另一个具有自己特定链接的 BeamLocation
,因此当我进入以下路径时:
/home/chefMainWidget/chefStoreHomePage/chefStoreMenu
从 chefStoreMenu
到 chefStoreHomePage
的返回按钮上点击返回到前一页是没有问题的。但是,在手机上点击返回按钮后,再次返回,即不是返回到 chefStoreHomePage
,而是直接返回到主页,这是错误的,应该显示页面。因此,主页和 chefStoreHomePage
页面都有自己特定的 BeamLocations
。
主页的 BeamLocation 是:
class HomeScreenTab extends BeamLocation<BeamState> {
HomeScreenTab(super.routeInformation);
@override
List<String> get pathPatterns => [
'/home/chefMainWidget/:storeId',
];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
const BeamPage(
key: ValueKey('/home'),
type: BeamPageType.noTransition,
child: HomeRootScreenWidget(),
),
);
if (state.uri.pathSegments.length > 1) {
String key = '';
Widget? screen;
switch (state.uri.pathSegments[1]) {
case 'chefMainWidget':
final storeId = state.pathParameters['storeId'];
key = '/home/chefMainWidget-$storeId-${DateTime.now()}';
screen = storeId == null ? null : ChefRootWidget(storeId: int.tryParse(storeId));
break;
}
if (screen != null) {
pages.add(BeamPage(
key: ValueKey(key),
type: BeamPageType.slideRightTransition,
child: screen,
));
}
}
return pages;
}
}
ChefRootWidget
类:
class ChefRootWidget extends StatefulWidget {
final int? storeId;
const ChefRootWidget({super.key, required this.storeId});
@override
State<StatefulWidget> createState() => _ChefRootWidget();
}
class _ChefRootWidget extends State<ChefRootWidget> {
int get storeId => widget.storeId!;
late List<BeamerDelegate> _routerDelegates;
@override
void initState() {
_routerDelegates = [
BeamerDelegate(
initialPath: '/home/chefMainWidget/chefStoreHomePage',
locationBuilder: (routeInformation, _) {
if (routeInformation.uri.toString().contains('/home/chefMainWidget/chefStoreHomePage')) {
return ChefStoreBeamer(routeInformation, storeId);
}
return NotFound(path: routeInformation.uri.toString());
},
),
];
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Beamer(
routerDelegate: _routerDelegates[0],
),
);
}
}
chefStoreHomePage
页面的 BeamLocation 是:
class ChefStoreBeamer extends BeamLocation<BeamState> {
final int storeId;
ChefStoreBeamer(super.routeInformation, this.storeId);
@override
List<String> get pathPatterns => [
'/home/chefMainWidget/chefStoreHomePage/chefStoreMenu/:menuId',
];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
BeamPage(
key: ValueKey('/home/chefMainWidget/chefStoreHomePage-$storeId-${DateTime.now()}'),
type: BeamPageType.slideRightTransition,
child: ChefStoreHomePageWidget(storeId: storeId),
),
);
if (state.uri.pathSegments.contains('chefStoreMenu')) {
final menuId = state.pathParameters['menuId'];
if (menuId != null) {
pages.add(
BeamPage(
key: ValueKey('/home/chefMainWidget/chefStoreHomePage/chefStoreMenu-$menuId-${DateTime.now()}'),
type: BeamPageType.slideRightTransition,
child: ChefMenuWidget(menuId: int.tryParse(menuId)),
),
);
}
}
return pages;
}
}
我使用 Beamer.of(context).beamToNamed
在页面之间进行导航,例如:
Beamer.of(context).beamToNamed('/home/chefMainWidget/chefStoreHomePage/chefStoreMenu/$menuId');
此外,这是一个示例代码。
英文:
In my application, at the beginning, I have 3 pages with the following routes:
routerDelegate = BeamerDelegate(
initialPath: initialPath,
locationBuilder: RoutesLocationBuilder(
routes: {
'/signup': (_, __, ___) => const SignupScreenWidget(),
'/verifyCode': (_, __, ___) => const VerifyCodeRootScreenWidget(),
'/home': (_, __, ___) => const HomeScreen(),
},
),
);
Inside the Home page, I defined the following BeamLocations
, and since I have another BeamLocation
inside the ChefRootWidget
that has its own specific links, when I go to the following path:
/home/chefMainWidget/chefStoreHomePage/chefStoreMenu
There is no problem returning to the previous page by clicking on the back icon from chefStoreMenu
to chefStoreHomePage
. However, after touching the back button on the phone, the return occurs again, i.e., instead of returning to chefStoreHomePage
, I am directly taken to the home page, which is wrong, and the page should be displayed. Therefore, both the home and chefStoreHomePage
pages have their own specific BeamLocations
.
The BeamLocation of the Home page is:
class HomeScreenTab extends BeamLocation<BeamState> {
HomeScreenTab(super.routeInformation);
@override
List<String> get pathPatterns => [
'/home/chefMainWidget/:storeId',
];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
const BeamPage(
key: ValueKey('/home'),
type: BeamPageType.noTransition,
child: HomeRootScreenWidget(),
),
);
if (state.uri.pathSegments.length > 1) {
String key = '';
Widget? screen;
switch (state.uri.pathSegments[1]) {
case 'chefMainWidget':
final storeId = state.pathParameters['storeId'];
key = '/home/chefMainWidget-$storeId-${DateTime.now()}';
screen = storeId == null ? null : ChefRootWidget(storeId: int.tryParse(storeId));
break;
}
if (screen != null) {
pages.add(BeamPage(
key: ValueKey(key),
type: BeamPageType.slideRightTransition,
child: screen,
));
}
}
return pages;
}
}
and ChefRootWidget
class:
class ChefRootWidget extends StatefulWidget {
final int? storeId;
const ChefRootWidget({super.key, required this.storeId});
@override
State<StatefulWidget> createState() => _ChefRootWidget();
}
class _ChefRootWidget extends State<ChefRootWidget> {
int get storeId => widget.storeId!;
late List<BeamerDelegate> _routerDelegates;
@override
void initState() {
_routerDelegates = [
BeamerDelegate(
initialPath: '/home/chefMainWidget/chefStoreHomePage',
locationBuilder: (routeInformation, _) {
if (routeInformation.uri.toString().contains('/home/chefMainWidget/chefStoreHomePage')) {
return ChefStoreBeamer(routeInformation, storeId);
}
return NotFound(path: routeInformation.uri.toString());
},
),
];
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Beamer(
routerDelegate: _routerDelegates[0],
),
);
}
}
The BeamLocation of the chefStoreHomePage page is:
class ChefStoreBeamer extends BeamLocation<BeamState> {
final int storeId;
ChefStoreBeamer(super.routeInformation, this.storeId);
@override
List<String> get pathPatterns => [
'/home/chefMainWidget/chefStoreHomePage/chefStoreMenu/:menuId',
];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
BeamPage(
key: ValueKey('/home/chefMainWidget/chefStoreHomePage-$storeId-${DateTime.now()}'),
type: BeamPageType.slideRightTransition,
child: ChefStoreHomePageWidget(storeId: storeId),
),
);
if (state.uri.pathSegments.contains('chefStoreMenu')) {
final menuId = state.pathParameters['menuId'];
if (menuId != null) {
pages.add(
BeamPage(
key: ValueKey('/home/chefMainWidget/chefStoreHomePage/chefStoreMenu-$menuId-${DateTime.now()}'),
type: BeamPageType.slideRightTransition,
child: ChefMenuWidget(menuId: int.tryParse(menuId)),
),
);
}
}
return pages;
}
}
What I do is use Beamer.of(context).beamToNamed
to move between pages, for example:
Beamer.of(context).beamToNamed('/home/chefMainWidget/chefStoreHomePage/chefStoreMenu/$menuId');
and then its full sample code:
import 'package:beamer/beamer.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class ALocation extends BeamLocation<BeamState> {
ALocation(super.routeInformation);
@override
List<String> get pathPatterns => ['/a/child-root'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
const BeamPage(
key: ValueKey('/a'),
type: BeamPageType.noTransition,
child: RootScreen(),
),
);
if (state.pathPatternSegments.contains('child-root')) {
pages.add(
const BeamPage(
key: ValueKey('/a/child-root'),
type: BeamPageType.noTransition,
child: ChildScreen(),
),
);
}
return pages;
}
}
class RootBeamerScreen extends StatefulWidget {
const RootBeamerScreen({super.key});
@override
State<RootBeamerScreen> createState() => _RootBeamerScreenState();
}
class _RootBeamerScreenState extends State<RootBeamerScreen> {
final _routerDelegates = [
BeamerDelegate(
initialPath: '/a',
locationBuilder: (routeInformation, _) {
if (routeInformation.uri.toString().contains('/a')) {
return ALocation(routeInformation);
}
return NotFound(path: routeInformation.uri.toString());
},
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Beamer(
routerDelegate: _routerDelegates[0],
),
);
}
}
class RootScreen extends StatelessWidget {
const RootScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('root'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () => Beamer.of(context).beamToNamed('/a/child-root'),
child: const Text('View child-root'),
),
],
),
),
);
}
}
class ChildLocation extends BeamLocation<BeamState> {
ChildLocation(super.routeInformation);
@override
List<String> get pathPatterns => ['/child-root/detail'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
List<BeamPage> pages = [];
pages.add(
const BeamPage(
key: ValueKey('/child-root'),
type: BeamPageType.noTransition,
child: ChildDetailWidget(),
),
);
if (state.pathPatternSegments.contains('detail')) {
pages.add(
const BeamPage(
key: ValueKey('/child-root/detail'),
type: BeamPageType.noTransition,
child: DetailScreen(),
),
);
}
return pages;
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('child screen')),
body: const SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Yooohooooooooo'),
],
),
),
);
}
}
class ChildScreen extends StatefulWidget {
const ChildScreen({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _ChildScreen();
}
class _ChildScreen extends State<ChildScreen> {
final _routerDelegates = [
BeamerDelegate(
initialPath: '/child-root',
locationBuilder: (routeInformation, _) {
if (routeInformation.uri.toString().contains('/child-root')) {
return ChildLocation(routeInformation);
}
return NotFound(path: routeInformation.uri.toString());
},
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Beamer(
routerDelegate: _routerDelegates[0],
),
);
}
}
class ChildDetailWidget extends StatelessWidget {
const ChildDetailWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('child screen')),
body: SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () =>
Beamer.of(context).beamToNamed('/child-root/detail'),
child: const Text('Click here'),
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final routerDelegate = BeamerDelegate(
initialPath: '/a',
locationBuilder: RoutesLocationBuilder(
routes: {
'*': (context, state, data) => const RootBeamerScreen(),
},
),
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.indigo),
routerDelegate: routerDelegate,
routeInformationParser: BeamerParser(),
backButtonDispatcher: BeamerBackButtonDispatcher(
delegate: routerDelegate,
),
);
}
}
答案1
得分: 2
只需在您的ChildDetailWidget
小部件内的此行代码中使用参数beamBackOnPop: true
:Beamer.of(context).beamToNamed('/child-root/detail', beamBackOnPop: true)
。发生的情况是,当您单击应用程序栏的返回按钮时,它会从Android的堆栈中弹出,但由于beamBackOnPop
的默认值为false,beamer不会返回,而且仍然在其堆栈中有DetailScreen
(但可见视图是ChildDetailWidget
)。之后,当您单击Android的返回按钮时,仍然有DetailScreen
的beamer会返回到ChildDetailWidget
(之前已经可见)。
在示例文档中提到了这一点。在那里查找Deep Location示例,他们在那里说明了以下内容:
Deep Location:您可以立即跳转到应用程序中的一个位置,其中有许多堆叠的页面(深度链接),然后逐个弹出它们,或者简单地使用
beamBackOnPop
参数覆盖AppBar的弹出以返回到您来自的位置。
英文:
You just need to use parameter beamBackOnPop: true
in this line of code inside your ChildDetailWidget
widget Beamer.of(context).beamToNamed('/child-root/detail',beamBackOnPop: true)
. <br>What's happening is that when you click app bar's back button then it's popped up from android's stack but because the default value of beamBackOnPop is false, beamer doesn't beam back and it still has DetailScreen
in it's stack (but visible view is ChildDetailWidget
) . After that when you click android's back button then beamer which still has DetailScreen
, beams back to ChildDetailWidget
(which was already previously visible).
They have mentioned in the examples docs. Look for Deep Location example there where they state the following<br>
> Deep Location: you can instantly beam to a location in your app that
> has many pages stacked (deep linking) and then pop them one by one or
> simply beamBack to where you came from. Note that beamBackOnPop
> parameter of beamToNamed might be useful here to override AppBar's pop
> with beamBack.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论