Flutter How does flutter implement partial-routing (partial-view)? - flutter

When push to SecondPage page:
I press the physical button to go back and it will go back to the desktop,
When I click the pop in second page button, it returns to the FirstPage page.
How can I do it so that when I press the physical button, I also go back to the FirstPage?
And after I click pop in first page, the body section will have nothing.
Is this a bug or?
Is there any other better way to implement partial-routing (partial-view)?, like a folder, pressing the return key can go back to the previous folder?
import 'package:flutter/material.dart';
class FlutterTest extends StatelessWidget {
const FlutterTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Navigator(
initialRoute: '/',
onGenerateRoute: (rs) {
return MaterialPageRoute(builder: (ctx) => const FirstPage());
},
),
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
const Text('this first page'),
TextButton(
child: const Text('pop in first page'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: const Text('push second page'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (ctx) => const SecondPage()));
},
),
],
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
const Text('this second page'),
TextButton(
child: const Text('pop in second page'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
}
}

Related

restorablePush then pushAndRemoveUntil results in error (flutter)

What I want to achieve:
Screen 1 is the initial screen, and goes to Screen 2 (via restorablePush)
Screen 2 is a restorable page, and goes to Screen 3 (via restorablePush)
Screen 3 is a restorable page, and goes back to Screen 1 (via pushAndRemoveUntil)
The restoring stuff works fine, but when I go back to Screen 1 from Screen 3 then kill the app and reopen it, I get this failed assertion error:
_history.isNotEmpty:
All routes returned by onGenerateInitialRoutes are not restorable.
Please make sure that all routes returned by onGenerateInitialRoutes
have their RouteSettings defined with names that are defined in the
app's routes table.
I've look at onGeneralInitialRoutes but I can't figure out how to solve this. I also tried doing everything with named routes, but it didn't change anything.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Screen1(),
restorationScopeId: 'root',
);
}
}
class Screen1 extends StatefulWidget {
const Screen1({Key? key}) : super(key: key);
#override
State<Screen1> createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
static Route<void> _myRouteBuilder(BuildContext context, Object? arguments) {
return MaterialPageRoute<void>(
builder: (BuildContext context) => const Screen2(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () {
Navigator.restorablePush(context, _myRouteBuilder);
},
child: const Text('Go to Screen 2')),
),
);
}
}
class Screen2 extends StatefulWidget {
const Screen2({Key? key}) : super(key: key);
#override
State<Screen2> createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
static Route<void> _myRouteBuilder(BuildContext context, Object? arguments) {
return MaterialPageRoute<void>(
builder: (BuildContext context) => const Screen3(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Screen 2')),
body: Center(
child: TextButton(
onPressed: () => Navigator.restorablePush(context, _myRouteBuilder),
child: const Text('Go to Screen 3'),
)));
}
}
class Screen3 extends StatelessWidget {
const Screen3({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Screen 3')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const Screen1()),
((route) => false)),
child: const Text('Go back to Screen 1'),
),
));
}
}

Close Multiple Bottom Sheet Modals in Flutter

I have two bottom sheet modals on a page. In the second modal, I do an action with cubit. On the main page, I listen to the event emitted from cubit, I want to close those two modals from the main page. I have tried these ways:
context.popRoute();
return context.read<BillCubit>().fetch();
or
context
..popRoute()
..popRoute();
return context.read<BillCubit>().fetch();
Both of those codes only close the second modal (last modal).
How to deal with it?
Just use Navigator.of(context).popUntil(ModalRoute.withName('/')); where '/' is the page route before the modals. This is going to pop all modals until it reaches the specified route.
Check out below a minimal reproducible example (also the live demo on DartPad)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _showModalBottomSheet({int count = 1}) {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
color: Colors.amber,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('ModalBottomSheet $count'),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _showModalBottomSheet(count: count + 1),
child: const Text('+1'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close this'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
// Pop until the initial page... in that case the root page
Navigator.of(context).popUntil(ModalRoute.withName('/'));
},
child: const Text('Close all'),
),
],
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
'Click + to show ModalBottomSheet',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _showModalBottomSheet,
tooltip: 'Show ModalBottomSheet',
child: const Icon(Icons.add),
),
);
}
}

pushReplacementNamed doesn't replace it just pushes on top if coming from an AlertDialog

Here's the code:
3 FILES home, main, and screen1
MAIN:
import 'package:flutter/material.dart';
import 'home.dart';
import 'screen1.dart';
void main() {
runApp(MaterialApp(
home: Home(),
routes:
{
'/home' : (context) => Home(),
'/screen1': (context) => Screen1()
},
));
}
HOME:
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: (){Navigator.pushReplacementNamed(context, '/screen1');},
child: Text('screen 1'),
),
),
);
}
}
screen1:
import 'package:flutter/material.dart';
Widget homeDialog(BuildContext context){
return AlertDialog(
title: const Text('Do you want to exit'),
actions: [
ElevatedButton(
child: const Text('No'),
onPressed: (){
Navigator.pop(context);
},
),
ElevatedButton(
child: const Text('Yes'),
onPressed: (){
Navigator.pushReplacementNamed(context, '/home');
},
),
],
);
}
class Screen1 extends StatefulWidget {
const Screen1({Key? key}) : super(key: key);
#override
State<Screen1> createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('screen 1'),
leading: GestureDetector(
onTap: (){showDialog(context: context, builder: homeDialog);},
child: Icon(Icons.home)
),
),
);
}
}
This happens because showDialog(AlertDialog(...)) is itself going to be pushed as a new route. So, when doing Navigator.pushReplacementNamed inside the alert it is going to replace the alert itself, not the previous screen. That's why Home and Screen1 got stacked on each other.
To solve this problem an AlertDialog should only return by a Navigator.pop(context, result) passing the result as an argument of it. The sources are going to be the following:
AlertDialog
Widget homeDialog(BuildContext context) {
return AlertDialog(
title: const Text('Do you want to exit'),
actions: [
ElevatedButton(
child: const Text('No'),
onPressed: () => Navigator.pop(context, false),
),
ElevatedButton(
child: const Text('Yes'),
onPressed: () => Navigator.pop(context, true),
),
],
);
}
Screen1
class Screen1 extends StatefulWidget {
const Screen1({Key? key}) : super(key: key);
#override
State<Screen1> createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('screen 1'),
leading: GestureDetector(
onTap: () async {
bool? yes =
await showDialog<bool>(context: context, builder: homeDialog);
// `mounted` checks if this screen hasn't been disposed already.
if (mounted) {
if (yes == true) {
Navigator.pushReplacementNamed(context, '/home');
} else {
Navigator.pop(context);
}
}
},
child: Icon(Icons.home)),
),
);
}
}

Show Snackbar on top route after closing a page

In my app, I have some areas where I can open a new page on top of the current, that allow to edit data. Once editing is done, I want to close the page (i.e. via Navigator.pop(context);), and also show a Snackbar after closing (i.e. via ScaffoldMessenger.of(context).showSnackBar('X has been saved')). I am using a ScaffoldMessenger for that.
However, if after closing the edit-page only the top-route remains, the Snackbar will not be shown. If I open any other page fast enough, it will be shown there for the remaining time though. So it was triggered, it is just not shown on the top-route. Also, if I open the edit-page not from the top-route, but from any other page that was already opened on top, the Snackbar will show normally after closing the edit-page.
If I open a Snackbar directly on the top-route, it also works fine. So instead of opening the Snackbar from the edit-page, I could technically return the message and then trigger the Snackbar. But I would prefer not to to pass data around and call functionality at several places, but just call the method at one place (where it belongs).
I can reproduce this behaviour on a newly created App, just need to replace the _MyHomePageState with the following code. What am I doing wrong here?
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<ScaffoldMessengerState> _globalScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: _globalScaffoldMessengerKey,
child: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push<bool>(context, MaterialPageRoute(builder: (context) => const SubPage()));
},
child: const Text("Open Subpage"),
),
),
),
);
}
}
class SubPage extends StatelessWidget {
const SubPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Hello Snackbar')));
},
child: const Text("Close Subpage"),
),
),
);
}
}
Remove the scaffold Messenger widget from the first page
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => const SubPage()));
},
child: const Text("Open Subpage"),
),
),
);
}
}
class SubPage extends StatelessWidget {
const SubPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Hello Snackbar')));
},
child: const Text("Close Subpage"),
),
),
);
}
}
I checked this code and it shows snackbar in the page that exists after popping the subpage

How to implement modal bottom sheet with fab similar to flutter_speed_dial?

Can someone tel me how to implement modal bottom sheet with fab similar to flutter_speed_dial (https://pub.dev/packages/flutter_speed_dial). To show the bottom sheet instead of icons while fab is animated
You can try doing it like this
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Bottom Sheet"),
),
body: const Center(
child: Text("Show Bottom Sheet"),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_showModalBottomSheet();
},
),
);
}
_showModalBottomSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return Wrap(
children: const [
ListTile(
leading: Icon(Icons.share),
title: Text('Share'),
),
ListTile(
leading: Icon(Icons.copy),
title: Text('Copy Link'),
),
ListTile(
leading: Icon(Icons.edit),
title: Text('Edit'),
),
],
);
},
);
}
}