I am building a responsive app, using auto_route.
The application is a simple master/detail view. My requirement is that when in desktop mode, the master (list) will be displayed on the left, and the detail on the right, in a "split screen" style.
When in mobile mode, the application will display the master and list as sequential views, pushed and popped onto a navigator (i.e, on screen one at a time).
I achieve this with the folowing: When in "desktop" mode, it will wrap the route with a "split screen" page. The split screen page has the list hard coded, and a child AutoRouter on the right.
When in "mobile" mode, it will wrap the route with a "stack screen" page. The stack screen is just a simple stack.
Now, I need to somehow restore the state between mobile and desktop modes, such that the path is retained.
i.e, if in Desktop mode, and your path is /detail/:5, you will see the split screen, list on the left, detail "5" on the right. Then you resize into mobile mode (this would happen when rotating a tablet for example) - your path remains the same, so the application switches you to the stack view, and restores a backstack with the "master list", and the "detail 5" pushed on top.
Ideas?
The author of AutoRoute has kindly offered a clean implementation which I have moved ahead with, and verified it works. (Thank you Milad)
Details and a short demo video are here: https://github.com/Milad-Akarie/auto_route_library/discussions/1667
For the sake of SO history, here's a copy of the code
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'demo_router.gr.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({super.key});
final router = DemoRouter();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router.config(),
);
}
}
@AutoRouterConfig()
class DemoRouter extends $DemoRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(
page: MasterRoute.page,
initial: true,
children: [
AutoRoute(page: DetailsRoute.page, path: 'details'),
AutoRoute(page: SubDetailsRoute.page, path: 'sub-details'),
],
),
];
}
@RoutePage()
class MasterScreen extends StatelessWidget {
const MasterScreen({super.key});
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
final splitMode = screenWidth > 600;
final splitPosition = splitMode ? screenWidth / 2 : 0.0;
return AutoRouter(
builder: (context, subRouterWidget) {
final subRouter = context.router;
// in split mode we show the back button only if the sub router can pop (entries.length > 1)
final showBackButton = splitMode ? subRouter.canPop() : subRouter.hasEntries;
return Scaffold(
appBar: AppBar(
/// we use router.popForced to always pop even if we have one entry in the stack
leading: showBackButton ? BackButton(onPressed: subRouter.popForced) : null,
),
body: Stack(
children: [
Positioned.fill(
right: splitPosition,
child: MasterScreenContent(),
),
Positioned.fill(
left: splitPosition,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
transitionBuilder: (child, animation) {
/// mimic route transition
return SlideTransition(
position: Tween<Offset>(
begin: Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
child: subRouter.hasEntries ? subRouterWidget : SizedBox(),
),
),
],
),
);
},
);
}
}
class MasterScreenContent extends StatelessWidget {
const MasterScreenContent({super.key});
@override
Widget build(BuildContext context) {
return ColoredBox(
color: Colors.grey.shade300,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text('Show details'),
onPressed: () {
context.navigateTo(DetailsRoute());
},
),
],
),
);
}
}
@RoutePage()
class DetailsScreen extends StatefulWidget {
const DetailsScreen({super.key});
@override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Details'),
Text('$_counter'),
SizedBox(height: 10),
ElevatedButton(
child: Text('Increment'),
onPressed: () {
setState(() {
_counter++;
});
},
),
SizedBox(height: 10),
ElevatedButton(
child: Text('Sub Details'),
onPressed: () {
context.pushRoute(SubDetailsRoute());
},
),
],
),
),
);
}
}
@RoutePage()
class SubDetailsScreen extends StatelessWidget {
const SubDetailsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Text('Sub Details'),
),
);
}
}