I am using flutter full_pdf_viewer package to view a pdf before saving. I have a save option which I would like to have a confirmation before saving. However, when I try to render the alert it does not show up. Currently, I have a workaround where I pop the PDFViewerScaffold before showing the alert however this is not the intended behavior. This is the code I currently have
class EditorPage extends StatefulWidget {
const EditorPage({
Key key,
}) : super(key: key);
#override
_EditorPageState createState() => _EditorPageState();
}
class _EditorPageState extends State<EditorPage> {
#override
Widget build(BuildContext context) {
final Map arguments = ModalRoute.of(context).settings.arguments as Map;
final pdfPath = arguments['pdfPath'];
return Scaffold(
body: PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
InkWell(
child: Icon(Icons.save),
onTap: () {
Navigator.of(context).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text('Enter the title of your notice'),
content: Text('This is a test'),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
},
),
],
),
path: pdfPath),
);
}
}
Try to add a key to your Scaffold:
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
And in your Scaffold:
return Scaffold(
key: _scaffoldKey,
This solve the problem here.
Related
I'm using go_router and I am about to do this in a callback of one of my buttons:
EvelatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/page-2');
},
)
This is to push 2 pages in the history at once. After the user click on this button, he ends up on the page page-2 and when he pops the page, there is page-1.
Is it acceptable to do that or is there any reason not to do it?
What would be those reasons and what should I do instead?
I don't think I've seen anything like that in go_router's examples.
For more context, here is a code snippet (or checkout https://github.com/ValentinVignal/flutter_app_stable/tree/go-router/push-twice-at-once):
When the button is pressed, I want to display the dialog page with the page-1 in the background.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
initialLocation: '/page-0',
routes: [
GoRoute(
path: '/page-0',
builder: (_, __) => const Page0Screen(),
),
GoRoute(
path: '/page-1',
builder: (_, __) => const Page1Screen(),
),
GoRoute(
path: '/dialog',
pageBuilder: (context, state) => DialogPage(
key: state.pageKey,
child: const DialogScreen(),
),
),
],
);
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 MaterialApp.router(
routerConfig: router,
);
}
}
class Page0Screen extends StatelessWidget {
const Page0Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 0')),
body: Center(
child: ElevatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/dialog');
},
child: const Text('Push'),
),
),
);
}
}
class Page1Screen extends StatelessWidget {
const Page1Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: const Center(
child: Text('Page 1'),
),
);
}
}
class DialogScreen extends StatelessWidget {
const DialogScreen({super.key});
#override
Widget build(BuildContext context) {
return const AlertDialog(
title: Text('Dialog'),
);
}
}
class DialogPage extends Page {
const DialogPage({
required this.child,
super.key,
});
final Widget child;
#override
Route createRoute(BuildContext context) {
return DialogRoute(
settings: this,
context: context,
builder: (context) {
return child;
},
);
}
}
Assuming your goal is to display a dialog you can use the showDialog function in flutter.
Below is a sample
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Basic dialog title'),
content: const Text('A dialog is a type of modal window that\n'
'appears in front of app content to\n'
'provide critical information, or prompt\n'
'for a decision to be made.'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Disable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Enable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
],
);
},
);
go_router doesn't support pushing two routes at the same time. And it is not a good practice to push 2 pages at the same time.
What can you do instead?
You can transition from page1 to page2
Go to dialog page in the init method of the page2 using context.go('/dialog');
On exiting dialog page you can use context.pop() which will land you in page1
I have implemented a Navigation Drawer in my app.
There I have included a column as menu with some options
Here you have a screenshot from the Navigation Drawer:
If I click on an option a new screen is opened
The isssue I need to solve is that when the user clicks on the back button of that screen, the app shows the default screen not the navigation drawer.
What should I do to go back to the navigation drawer when clicking on the back button?
EDIT
This is how am I calling each class to be opened from the Navigation Drawer:
void onItemPressed(BuildContext context, {required int index}){
Navigator.pop(context);
switch(index){
case 1:
Navigator.push(context, MaterialPageRoute(builder: (context) => const GestionUsuariosInternos()));
break;
case 2:
Navigator.push(context, MaterialPageRoute(builder: (context) => const GestionUsuariosExternos()));
break;
case 3:
Navigator.push(context, MaterialPageRoute(builder: (context) => const GestionUsuariosVisitantes()));
break;
case 4:
Navigator.push(context, MaterialPageRoute(builder: (context) => const GestionEmpresas()));
break;
case 8:
Navigator.push(context, MaterialPageRoute(builder: (context) => Mapa()));
break;
}
}
EDIT
This is the code from one of the pages that is opened from the navigation drawer
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class Mapa extends StatefulWidget {
#override
State<Mapa> createState() => MapaState();
}
class MapaState extends State<Mapa> {
Completer<GoogleMapController> _controller = Completer();
static const CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
static final CameraPosition _kLake = CameraPosition(
bearing: 192.8334901395799,
target: LatLng(37.43296265331129, -122.08832357078792),
tilt: 59.440717697143555,
zoom: 19.151926040649414);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
title: Text("Sample"),
centerTitle: true,
),
body: GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _goToTheLake,
label: Text('To the lake!'),
icon: Icon(Icons.directions_boat),
),
);
}
Future<void> _goToTheLake() async {
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
}
}
You can use something called WillPopScope widget for that,
you can perform any operation when pressed back button.
You should wrap you screens with this widget and should make Navigation drawer available to every screen.
https://api.flutter.dev/flutter/widgets/WillPopScope-class.html
You can use WillPopScope widget to handle backbutton press event.Wrap the the WillPopScope widget on root of the pages widget. like this
class Page1 extends StatelessWidget {
Page1({Key? key}) : super(key: key);
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
_globalkey.currentState?.openDrawer();
// Scaffold.of(context).openDrawer();
return Future.value(false);
},
child: new Scaffold(
key: _globalkey,
drawer: Drawers.getDrawer(context),
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
title: Text("Pag1"),
centerTitle: true,
),
body: Container(),
),
);
}
}
to open a drawer while press backbutton
_globalkey.currentState?.openDrawer();
_globalkey is scaffoldstate type Globalkey .you can declare it inside the widget page class or outside.
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
Sample Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(home: MYAppWithoutFlicker()));
}
class MYAppWithoutFlicker extends StatefulWidget {
MYAppWithoutFlicker({Key? key}) : super(key: key);
#override
State<MYAppWithoutFlicker> createState() => _MYAppWithoutFlickerState();
}
class Drawers {
static Widget getDrawer(BuildContext context) {
return Container(
width: 100,
child: Drawer(
child: ListView(
children: [
ListTile(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Mapa()));
},
title: Text(
"Pag1",
),
),
ListTile(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Page1()));
},
title: Text("Pag2"),
),
ListTile(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Page2()));
},
title: Text("Pag3"),
),
],
),
),
);
}
}
class _MYAppWithoutFlickerState extends State<MYAppWithoutFlicker> {
// var decode = (bytes, {allowUpscaling, cacheHeight, cacheWidth}) {
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
;
return Scaffold(
key: _globalkey,
appBar: AppBar(
title: Text("Home"),
),
drawer: Drawers.getDrawer(context),
body: ListView(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// shrinkWrap: false,
children: [
...List.generate(
10,
(index) => Container(
height: 100,
child: Card(
child: Center(
child: Text(
index.toString(),
style: TextStyle(fontSize: 25),
)),
),
))
],
),
);
}
#override
void initState() {}
}
class Page1 extends StatelessWidget {
Page1({Key? key}) : super(key: key);
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
_globalkey.currentState?.openDrawer();
// Scaffold.of(context).openDrawer();
return Future.value(false);
},
child: new Scaffold(
key: _globalkey,
drawer: Drawers.getDrawer(context),
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
title: Text("Pag1"),
centerTitle: true,
),
body: Container(),
),
);
}
}
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
// key: _globalkey,
drawer: Drawers.getDrawer(context),
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
title: Text("Pag2"),
centerTitle: true,
),
body: Container(),
);
}
}
class Mapa extends StatefulWidget {
#override
State<Mapa> createState() => MapaState();
}
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
class MapaState extends State<Mapa> {
GlobalKey<ScaffoldState> _globalkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
child: new Scaffold(
key: _globalkey,
drawer: Drawers.getDrawer(context),
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
title: Text("Sample"),
centerTitle: true,
),
body: Container(),
floatingActionButton: FloatingActionButton.extended(
onPressed: _goToTheLake,
label: Text('To the lake!'),
icon: Icon(Icons.directions_boat),
),
),
onWillPop: () {
_globalkey.currentState?.openDrawer();
// Scaffold.of(context).openDrawer();
return Future.value(false);
},
);
}
Future<void> _goToTheLake() async {}
}
Minimal code:
void main() => runApp(MaterialApp(home: MainPage()));
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Navigator.of(context, rootNavigator: true).pop());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
On pressing the button, I am showing dialog and after 2s I want to close the Drawer while keeping the Dialog opened on the screen. For this I am using Timer and rootNavigator property of Navigator. However, my dialog is getting dismissed.
Is there any solution for closing the drawer besides using GlobalKey<DrawerControllerState> stuff?
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: RaisedButton(
onPressed: () {
// Close the Drawer, not the Dialog.
Timer(Duration(seconds: 2), () => Scaffold.of(context).openEndDrawer());
// Show the Dialog and keep it in opened state.
showDialog(
context: context,
builder: (_) => AlertDialog(title: Text('FooDialog')),
);
},
child: Text('Show Dialog'),
),
);
}
}
You can use ScaffoldState, to close the drawer. Just keep a track of the time and you are good to go. In this answer, I have told you on how to use the ScaffoldState with your drawer.
This code will help you achieve what you want. I have used the second option from my previous answer, that is, using everything in the MainPage only
class MainPage extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
// this will check for the drawer state and close it
// using _scaffoldKey
timer() {
return Future.delayed(Duration(seconds: 2), (){
// checking whether it is open
if(_scaffoldKey.currentState.isDrawerOpen){
// here how you close it
_scaffoldKey.currentState.openEndDrawer();
}
});
}
Future<void> _showMyDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog Title'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('This is a demo alert dialog.'),
Text('Would you like to approve of this message?'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Our drawer
Drawer _drawer(BuildContext context) => Drawer(
child: RaisedButton(
onPressed: (){
timer();
_showMyDialog(context);
},
child: Text('Show Dialog'),
)
);
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(),
drawer: _drawer(context)
);
}
}
Result
Please note: I have not clicked anywhere on the screen, to close the drawer. It goes automatically by the timer()
Is it possible to return to the exact same place meaning state wise in flutter while using this?
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new ConnectHome(user:widget.user))));
We have cards on the home screen "ConnectHome()" and we need to return them to the same spot.
You can copy paste run full code below
You can await Navigator.push and In Navigator.pop include UserObject()
You can see the code continue execution and print UserObject()
code snippet
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => ConnectHome()),
);
print('result ${result.name}')
...
Navigator.pop(context, UserObject("hello","world"));
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => ConnectHome()),
);
print('result ${result.name}');
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("${result.name}")));
}
}
class UserObject {
String name;
String id;
UserObject(this.name, this.id);
}
class ConnectHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, UserObject("hello","world"));
},
child: Text('Hello'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result.
Navigator.pop(context, UserObject("no","No"));
},
child: Text('No.'),
),
)
],
),
),
);
}
}
I have a basic question about send setState
to Second Page in the same class as this method like
_GoToNextPage(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {...})
}
The problem is when I change background color in second page it doesn't
change color in the same page But it changes the color of The prime home page.
This is the full code...
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: SetStateToSecondPage(),
));
class SetStateToSecondPage extends StatefulWidget {
#override
_SetStateToSecondPageState createState() => _SetStateToSecondPageState();
}
class _SetStateToSecondPageState extends State<SetStateToSecondPage> {
Color color = Colors.deepPurpleAccent;
bool Switch = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
title: Text('setState to Second Page ?'),
elevation: 0.0,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
Switch = !Switch;
color = Switch ? Colors.white : Colors.green;
});
},
child: Text('Change Back GroundColor'),
),
RaisedButton(
onPressed: () {
_GoToNextPage(context);
},
child: Text('To Next Page..'),
),
],
),
),
);
}
//------------- This is second Page ----------//
_GoToNextPage(BuildContext context) {
return Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
backgroundColor: color,
body: Center(
child: RaisedButton(
onPressed: () {
setState(() {
color = Colors.red;
});
},
child: Text('Change Back GroundColor'),
),
),
);
}));
}
}
thanks
SetState is specific to the object you are in . and when you call it you notify the framework that the state has changed . so calling setState in _SetStateToSecondPageState will not affect Second Page so you need to create another StatefulWidget
class SecondPage extends StatefulWidget {
MaterialColor secondColor ;
SecondPage({this.secondColor});
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
backgroundColor: widget.secondColor,
body: Center(
child: RaisedButton(
onPressed: () {
setState(() {
widget.secondColor = Colors.amber;
});
},
child: Text('Change Back GroundColor'),
),
),
);
}
}
and when you call _GoToNextPage use the SecondPage constructor to change the color
_GoToNextPage(BuildContext context) {
return Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return SecondPage(
);
}));
}