I'm trying to use Navigator.push to navigate to a new page in Flutter. This is what I've got so far:
GestureDetector(
onTap: () {
print('Test');
// Navigator.push(context,
// MaterialPageRoute(builder: (context) => ResultsPage()));
},
child: Container(
color: Color(0xFFff474b),
child: Center(
child: Text('CALCULATE', style: kButtonText),
),
padding: EdgeInsets.only(bottom: 20.0),
width: double.infinity,
height: 80.0,
margin: EdgeInsets.only(top: 10.0),
),
),
The code that I want to implement is commented out, as I've been testing the onTap with a print statement.
Interestingly, the print statement runs but for some reason, I can't get the Navigator.push to work. It only navigates to a black screen.
For context, this is the Results page - just a simple scaffold with an App Bar. But the App Bar doesn't show up on the next page:
class ResultsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calculated Distance'),
),
);
}
}
The error may be caused due to two or more Floating Action buttons present in a scaffold. By default Floating Action button has a Hero property active. Make it deactivate by heroTag: null,.
For eg,
FloatingActionButton(
heroTag: null,
child: Icon(),
onPressed: () {},
),
Ahh it turns out that the issues is that I have two FloatingActionButtons on the page, and so it messes with the Navigator route.
This Medium article I found is a nice guide for the solution: https://medium.com/#kaendagger/test-cef30fcb5c54
add ResultsPage() to your routes add this to you MaterialApp(
MaterialApp(
initialRoute: '/',
routes: {
'/': (_) => HOME(),
'/home': (_) => ResultsPage(),
}, )
Related
I am building a RegisterPage in flutter and I devided my scaffold into two containers. One for the top half and the other for the bottom half so that the logo/headline can be in the top half and the form be in the bottom half.
Now I noticed after testing on android that the keyboard overlaps the input fields. So I added a SingleChildScrollView() view to fix this. But there is a problem. The content appears to be too big and you can now always scroll even when the keyboard is down. I added a video to demonstrate what I mean.
I think that this space is responsible for that
It looks like that space is reserved for the android navigation bar. How do I remove that? Or how can I size my containers in such a way so that they account for that? Just to be clear the thing I want to know most is how I can stop my SingleChildScrollView() scrolling when the keyboard is down, which is probably cause by the containers being to big.
the_register_page.dart
#override
Widget build(BuildContext context) {
final AuthenticationProvider authenticationProvider = Provider.of<AuthenticationProvider>(context);
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: ScrollConfiguration(
behavior: ScrollBehaviourWithoutGlow(),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Container(
decoration: BoxDecoration(color: Colors.red),
height: MediaQuery.of(context).size.height / 2,
child: _buildLogo(),
),
Container(
decoration: BoxDecoration(color: Colors.green),
height: MediaQuery.of(context).size.height / 2,
child: _buildRegisterForm(authenticationProvider),
),
],
),
),
),
),
);
}
Notice that I used the SystemChrome.setEnabledSystemUIOverlays([]); in my main.dart.
main.dart
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(
create: (_) => PreferencesProvider()),
ChangeNotifierProvider<AuthenticationProvider>(
create: (_) => AuthenticationProvider()),
Provider<GroupProvider>(create: (_) => GroupProvider()),
Provider<UserProvider>(
create: (_) => UserProvider(),
),
],
child: Consumer<PreferencesProvider>(
builder: (context, preferences, _) => MaterialApp(
home: TheSplashPage(),
routes: <String, WidgetBuilder>{
TheGroupPage.routeName: (BuildContext context) => TheGroupPage(),
TheSettingsPage.routeName: (BuildContext context) =>
TheSettingsPage(),
TheProfilePage.routeName: (BuildContext context) =>
TheProfilePage(),
TheGroupCreationPage.routeName: (BuildContext context) =>
TheGroupCreationPage(),
},
theme: preferences.isDarkMode
? DarkTheme.themeData
: LightTheme.themeData,
debugShowCheckedModeBanner: false,
),
),
);
}
}
Since this post didn't get any answers I decided to upload a video to show what I mean. Here is the video. As you can see you are able to swipe even when the keyboard is down which is not what I want.
https://www.youtube.com/watch?v=Rm91KMFqs60
Edit
This is tested on a android emulator
And my android device a Lenovo P2 with android 7
Edit
I can confirm that this bottom area is the cause. I looked at the pixel height on my pc screen and subtracted that from the container and now I can't scroll when the keyboard is down which is what I want.
So I would like to remove this area or get it's height any idea how?
You're very close. Setting the SystemChrome color to transparent just makes them see-through but won't draw your app over top of the navigation or status bars. The fix is a 2 step process.
Set SystemChrome.setEnabledSystemUIOverlays([SystemUIOverlay.top]) to display only the notification bar or SystemChrome.setEnabledSystemUIOverlays([]) to display your app in full screen.
In your Scaffold set the resizeToBottomInset property to false when you want the screen to display over top of the bottom system UI overlay and to true when the keyboard is up.
Full code example:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Display your app in full-screen mode
SystemChrome.setEnabledSystemUIOverlays([]);
// Show the notification bar but not the navigation bar
// SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool resizeToAvoidBottom;
#override
void initState() {
super.initState();
// Do not avoid bottom by default, this allows the display to paint over
// where the Android button bar would be.
resizeToAvoidBottom = false;
}
void setResize(bool resize) {
setState(() {
resizeToAvoidBottom = resize;
});
}
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height / 2;
return Scaffold(
resizeToAvoidBottomInset: resizeToAvoidBottom,
body: Container(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Placeholder(
fallbackHeight: height,
),
Container(
height: height,
color: Colors.green,
child: Column(
children: <Widget>[
// Text fields update the resize property so that you
// can still scroll the display when the keyboard is up
// You will need to change when setResize(false) is
// called to suit the needs of your app
TextField(
onTap: () {
setResize(true);
},
onSubmitted: (str) {
setResize(false);
},
),
TextField(
onTap: () {
setResize(true);
},
onSubmitted: (str) {
setResize(false);
},
),
],
))
],
),
),
),
);
}
}
This is actually a current Issue.
There is workaround which you could use, which is setting extendxBody: true in your Scaffold.
But if it is not extremely critical I would wait for the fix to be deployed.
To clarify: My answer is about the empty space at the bottom.
The API documentation: https://api.flutter.dev/flutter/material/Scaffold-class.html says:
The Scaffold was designed to be the single top level container for a
MaterialApp and it's typically not necessary to nest scaffolds. For
example in a tabbed UI, where the bottomNavigationBar is a TabBar and
the body is a TabBarView, you might be tempted to make each tab bar
view a scaffold with a differently titled AppBar. It would be better
to add a listener to the TabController that updates the AppBar.
Does it mean there needs to be only one single Scaffold under the Material App or one single parent Scaffold for each page. If it's the first, how do we navigate? If the it's later, doesn't it mean the common AppBar and BottomBar get re-rendered on each navigation? What's the best practice.
It means that, usually, there should be one Scaffold for each page (more precisely, for each Route/screen), and that you should not nest Scaffolds.
Navigating between screens
For example, take a look at this snippet that you can run in DartPad:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
Here, you can see there are two distinct pages/Routes/screens, and each one has its own Scaffold. We are navigating back and forth by using Navigator, and so our pages are being added to the stack, one Scaffold on top of the other. That is fine.
If the it's later, doesn't it mean the common AppBar and BottomBar get re-rendered on each navigation?
Yes, but that is precisely what we want when we make two separate screens, each one with its own Scaffold.
Navigating inside the body of a Scaffold / nested navigations
On the other hand, take a look at this example:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {},
),
),
),
);
}
}
Here, we are nesting two Scaffolds, and, as you can see, the second app bar is being drawn below the first app bar. That would not be the best approach for tabbed or nested navigations. If you want to navigate inside the body of a Scaffold, and change the app bar depending on the content, using TabControllers, such as DefaultTabController, is preferred. Take a look at this example:
import 'package:flutter/material.dart';
void main() {
runApp(TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
As you can see, we have used only one Scaffold, since we are dealing with only one screen, really. It just happens that we want to show content pages and navigate inside the body of the Scaffold.
Conclusion
As a general rule of thumb: use only one Scaffold per Route/screen. Use only one Scaffold with widgets such as TabController or IndexedStack to navigate the content inside the body of a single screen.
Hey friends on StackOverflow, how are you guys doing today! I am new to Flutter and I'm facing some issues with understanding this language. I am trying to make an overlay of a textfield of a small size when I click on a button. However, I am facing a No material widget found. TextField require a Material widget ancestor Can you guys please help me out with this? Thanks so much!
Happy Coding guys!
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "text page",
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appBar;
return new Scaffold(
appBar: AppBar(
title: Text("page"),
),
body: Center(
child: Container(
child: Text("This is a page blahblah"),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.message),
onPressed:(){
showOverlay(context);
},
),
);
}
//void method for overlay text field
void showOverlay(BuildContext context) async {OverlayState overlayState = Overlay.of(context);
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: 200.0,
left: 1.0,
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter text',
),
),
),
);
overlayState.insert(overlayEntry);
}
}```
As flutter said, TextField needs a material widget as parent. You can wrap your TextField in one of these widgets that has Material widget in them: Card , Dialog , Drawer , Scaffold . Or you can directly use Material.
void showOverlay(BuildContext context) async {
OverlayState overlayState = Overlay.of(context);
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Material( //Use a Material Widget
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter text',
),
),
),
);
overlayState.insert(overlayEntry);
}
I recommend using Dialog instead of overlay. Dialog don't cover whole screen and contains Material widget in itself. And it is more beautiful :).
I have a next RaisedButton to go a next screen called DetailOracion.
Based on the example of Flutter to push new screen but doest work.
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Oraciones Cristianas'),
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailOracion()));
},
child: Text('Hello Wolrd'),
)
],
),
)),
),
);
}
My DetailOracion
class DetailOracion extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Hola'),
),
body: Text('Segunda Pantalla'),
);
}
}
And the error message its the next
I/flutter ( 3441): The following assertion was thrown while handling a gesture:
I/flutter ( 3441): Navigator operation requested with a context that does not include a Navigator.
I/flutter ( 3441): The context used to push or pop routes from the Navigator must be that of a widget that is a
I/flutter ( 3441): descendant of a Navigator widget.
When used the MaterialPageRoute you need to send your main class inside of MaterialApp from runApp()
Explain with Code
Correct
void main() => runApp(MaterialApp(
title: 'Oraciones Cristianas',
home: MyApp(),
));
You send yout first Screen insde of MaterialApp(), able to use MaterialPageRoute()
Incorrect
void main() => runApp(myApp());
If you simply send your first screen without MaterialApp() wrapper doesnt works
Use a Builder around your button or around the Column as in the following code:
Builder(
builder: (context) => RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => SelectUserType()));
},
child: Text('Registrese'),
),
),
Is there any way I can catch the onBackPressed event from Android back button?
I've tried the WillPopScope but my onWillPop function only triggered when I tap on the Material back arrow button
I put it like this:
class MyView extends StatelessWidget{
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async {
debugPrint("Will pop");
return true;
},
child: ScopedModel<AppModel>(
model: new AppModel(),
child: new Scaffold(......
I need to catch it because somehow my screen behaved incorrectly when it came to back button pressed, it pops the screen and the screen below it, but somehow, using material back arrow button works normal.
Update:
The code works, my problem was not in the pop of this screen, but on the previous screen, I use 2 MaterialApp widgets, and somehow it gave a weird behavior.
In order to prevent navigating back WillPopScope is the correct way and should be used as follow:
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new WillPopScope(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Page 2'),
),
body: new Center(
child: new Text('PAGE 2'),
),
),
onWillPop: () async {
return false;
},
);
}
}
Future<T> pushPage<T>(BuildContext context, Widget page) {
return Navigator.of(context)
.push<T>(MaterialPageRoute(builder: (context) => page));
}
Can call the page like:
pushPage(context, Page2());
This is should be helpful.
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
_moveToScreen2(context, );
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
_moveToScreen2(context);
}),
title: Text("Screen 1"),
),
),
);
}
/**
* This is probably too thin to be in its own method - consider using
* `Navigator.pushReplacementNamed(context, "screen2")` directly
*/
void _moveToScreen2(BuildContext context) =>
Navigator.pushReplacementNamed(context, "screen2");
Use WillPopScope method and return false
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Do something here
print("After clicking the Android Back Button");
return false;
},
child: Scaffold(
appBar: AppBar(
title: Text("Handling the back button"),
),
body: Center(
child: Text("Body"),
),
),
);
}
Just adding an important point here.
Please note that by using WillPopScope, we will lose the back swipe gesture on iOS.
Reference: https://github.com/flutter/flutter/issues/14203
This code work for me.
I think there may be two reasons.
Child of WillPopScope is Scaffold
No return in onWillPop
return new WillPopScope(
onWillPop: () {
if (!_isOpened) Navigator.pop(context);
},
child: new Scaffold(
key: SharedService.orderScaffoldKey,
appBar: appBar,
body: new Builder(
builder: (BuildContext context) {
return page;
},
),
),
);
Another way todo this is to implement a NavigatorObserver and link it to the MaterialApp:
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html
You don't have to use RouteAware, you can also implement your own NavigatorObserver.
This is for example how Flutter analytics works to automatically track screen opens/closes:
MaterialApp(
...
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
)
FirebaseAnalyticsObserver extends the RouteObserver which itself implements NavigatorObserver.
However WillPopScope is often the easier solution
You can use back_button_interceptor
it detects hardware back button & will be so useful specially in case of using persistent_bottom_nav_bar
#override
void initState() {
super.initState();
BackButtonInterceptor.add(myInterceptor);
}
#override
void dispose() {
BackButtonInterceptor.remove(myInterceptor);
super.dispose();
}
bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
print("BACK BUTTON!"); // Do some stuff.
return false;// return true if u want to stop back
}
Following the documentation of BackButtonListener:
/// It can be useful for scenarios, in which you create a different state in your
/// screen but don't want to use a new page for that.
https://github.com/flutter/flutter/pull/79642
e.g.
#override
Widget build(BuildContext context) {
return BackButtonListener(
onBackButtonPressed: () {
/// todo: close search widget
if(searchBarController.isClose()){
return false;
}else{
searchBarController.close();
return Future.value(true);
}
},
child: SearchBar(controller: searchBarController),
);
}
This is the updated code
basically, WillPopScope -> onWillPop works on the future argument
we can say as when it happens then ????
so as soon the back button is pressed WillPopScope -> onWillPop gets activated and listens to the argument more specific the back button event to pop it (replace it)
Most of the time I use it to show a DialogBox of Future type because it will only appear when it is needed same can be used to navigate to a new screen as well (hope so) preferred to do MaterialPage routing or named routing techniques for navigation,
use WillPopScope for the hardware back button event listening (hardware = Android/IOs) to show the exit popup
code is already been given above but does not work for me so I change a little bit
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async{
return _moveToScreen2(context);
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
_moveToScreen2(context);
}),
title: Text("Screen 1"),
),
),
);
}
Future<bool>_moveToScreen2(BuildContext context) =>
Navigator.pushReplacementNamed(context, "screen2");
===============================================
What I do on Exit
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()=> showExitPopup(context)
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("Screen 1"),
),
),
);
}
=========================================
On Back button Press
created a dart file with the name showExitPopup
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:vexpositions/Servises/ConstantManager.dart';
Future<bool> showExitPopup(context) async{
return await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SizedBox(
height: 90,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Want to Exit the app!"),
const SizedBox(height:20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
print('yes selected');
exit(0);
},
style: ElevatedButton.styleFrom(
primary: Colors.white),
child: const Text("Yes", style: TextStyle(color:
Colors.black)),
),
),
const SizedBox(width: 15),
Expanded(
child: ElevatedButton(
onPressed: () {
print('no selected');
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
primary: Colors.red.shade800,
),
child: const Text("No", style: TextStyle(color:
Colors.white)),
))
],
)
],
),
),
);
});
}