Show a SnackBar Above Modal Bottom Sheet - flutter

I tried to display a SnackBar above my Modal Bottom Sheet, but it doesn't work. Any idea how to achieve that?
P.S.: Setting the SnackBar Behavior to Floating doesnt work. It still appears below the modal bottom sheet
Thank you

Just wrap your child widget with Scaffold
await showModalBottomSheet(
context: context,
builder: (builder) => Scaffold(
body: YourModalContentWidget()
)
);

You will need to use GlobalKey<ScaffoldState> to show the Snackbar in desired Scaffold, for that you can added Scaffold in your showModalBottomSheet like below snip;
Define you GlobalKey in your Statefull or Stateless widget class
final GlobalKey<ScaffoldState> _modelScaffoldKey = GlobalKey<ScaffoldState>();
And on button press you can have;
showModalBottomSheet(
context: context,
builder: (_) => Scaffold(
extendBody: false,
key: _modelScaffoldKey,
resizeToAvoidBottomInset: true,
body: FlatButton(
child: Text("Snackbar"),
onPressed: () {
_modelScaffoldKey.currentState.showSnackBar(SnackBar(
content: Text("Snackbar"),
));
},
)),
);
DartPad

Try setting a margin to the Snackbar according to the BottomSheets height:
SnackBar(behavior: SnackBarBehavior.floating, margin: EdgeInsets.fromLTRB(0, 0, 0, 500),)

Thanks #alexey i solve mine this way
showMaterialModalBottomSheet(
context: context,
enableDrag: false,
isDismissible: false,
expand: false,
builder: (context) => Container(
//padding: EdgeInsets.only(bottom: 20),
height: MediaQuery.of(context).size.height * 0.9, //bottom sheet height
color: Colors.black.withOpacity(0.0),
child: Scaffold(
body: NewTripPopupDialog(kInitialPosition: kInitialPosition))),
);

If you are willing to use third-party package, you can easily do this with GetX.
Just import the package in your pubspec.yaml file:
dependencies:
get: ^3.17.0
Change your MaterialApp to GetMaterialApp.
GetMaterialApp is necessary for routes, snackbars, internationalization, bottomSheets, dialogs, and high-level apis related to routes and absence of context.
And use Get.bottomSheet() and Get.snackbar(). You have a bunch of properties to customize both methods. If a BottomModalSheet is open and you create a Snackbar it automatically shown above the sheet.
Simple example:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Snackbar above Bottom Modal Sheet'),
),
body: SafeArea(
child: Center(
child: RaisedButton(
child: Text('Open Modal Sheet'),
onPressed: () {
// Bottom Modal Sheet
Get.bottomSheet(
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('My bottom modal sheet'),
RaisedButton(
child: Text('Show SnackBar'),
onPressed: () {
// Snackbar
Get.snackbar(
'Hey',
'I am a snackbar',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
);
},
),
],
),
backgroundColor: Colors.green,
);
},
),
),
),
);
}
}

Have you tried setting the behavior property to floating?
SnackBar(
behavior: SnackBarBehavior.floating
This should solve the issue

Related

How can I achieve a draggable header in bottomsheet modal?

In my flutter app, I’m using a pinned header and a listview within a bottomsheet modal.
The problem usually is that when I overscroll, I have to scroll all the way back to be able to dismiss the modal. I would like to drag the header instead to collapse the bottom sheet modal even when the list is overscrolled.
Does anyone know how I can make the header draggable to achieve this effect?
You can find similar behavior in for example "places" section on Snapchat or the "comments" section of YouTube’s mobile app.
Here is a preview: https://imgur.com/a/fxww2IW
Here is a link to my code snippet: https://dartpad.dev/?id=960bf30a2c288ec1a0a48374b6cdbfd3
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ElevatedButton(
child: const Text('Show modal'),
onPressed: () => {
showModalBottomSheet(
backgroundColor: Colors.transparent,
clipBehavior: Clip.antiAlias,
context: context,
enableDrag: true,
isDismissible: true,
isScrollControlled: true,
builder: (modalContext) => MyWidget())
},
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.5,
minChildSize: 0.5,
maxChildSize: 0.9,
expand: false,
builder: (BuildContext context, ScrollController scrollController) =>
Container(
color: Colors.white,
child: CustomScrollView(
controller: scrollController,
slivers: [
const SliverAppBar(
title: Text("Header Title"),
pinned: true,
),
SliverFillRemaining(
child: ListView.builder(
itemCount: 500,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
trailing: const Text(
"GFG",
style: TextStyle(color: Colors.green, fontSize: 15),
),
title: Text("List item $index"));
}),
),
],
),
),
);
}
}
I have tried using CustomScrollview with Slivers. I expect that dragging the pinned header downwards will also move the bottomsheet downwards as in the image I have shared in the description
Look The solution for your problem is easy make a late ScrollController _scrollController; and put it in your listviewthen put this code in initState
super.initState();
_controller = ScrollController()..addListener(_scrollListener);
void _scrollListener() {`
if (_controller.position.userScrollDirection == ScrollDirection.forward &&
_controller.position.extentAfter >= _controller.position.maxScrollExtent) {
Navigator.pop(context);
}
`}
this mean when my ScrollController go up a little pop out that will close your showModalBottomSheet

How to keep the hamburger icon in my flutter app after I go another stateful widget?

So I have a drawer and I can open it with the hamburger icon on top-left corner. If I change the screen to another page, this hamburger icon become a back button (<--). How to make this into a hamburger icon again? (So no back button but open the drawer on every screen)
In App Bar of next page put
leading: BurgerIcon(),
The Burger icon is from having a Drawer on your Scaffold.
If your next page/route has a Scaffold with a drawer: specified, you'll get the "burger" icon button again.
Without a drawer: specified on Scaffold Flutter will default to the back arrow to go to the previous route.
Here's an example:
import 'package:flutter/material.dart';
class DrawerDirectoryPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Drawer Directories'),
),
body: Center(
child: InkWell(
child: Text('Page One. Click to go to Page Two.'),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => PageTwo())
),
),
),
drawer: MyDrawerDirectory(),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Burger Time!'),
),
body: Center(
child: Text('Page TWO /flex'),
),
drawer: MyDrawerDirectory(), // ← Drawer Directory a.k.a. burger icon
);
}
}
class MyDrawerDirectory extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: [
ListTile(title: Text('Page One'), onTap: () => _navPush(context, DrawerDirectoryPage())),
ListTile(title: Text('Page Two'), onTap: () => _navPush(context, PageTwo()))
],
),
);
}
Future<dynamic> _navPush(BuildContext context, Widget page) {
return Navigator.push(context, MaterialPageRoute(
builder: (context) => page,
));
}
}
In the AppBar widget, you can specify a leading widget. After you specify a leading widget (in your case, an Icon Button), you should set AppBar's automaticallyImplyLeading = false.
If you use the methods Navigator.of(context).push or Navigator.of(context).pushNamed you will also experience this issue.
You can use other method to navigate between screens so it doesn't show the hamburger button
Put automaticallyImplyLeading: false in appBar of next page then set leading icon like this:
automaticallyImplyLeading: false,
leading: Padding(
padding: const EdgeInsets.only(right: 20.0),
child: GestureDetector(
onTap: () {
if (scaffoldKey.currentState.isDrawerOpen) {
Navigator.of(context).pop();
} else {
scaffoldKey.currentState.openDrawer();
}
},
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Icon(
Icons.menu,
size: 30,
color: appColor,
),
),
),
),

Flutter - Detect Tap on the Screen that is filled with other Widgets

I am trying to detect a tap on the screen. I've tried multiple variation of using the GestureDetector but that just leads to the app detecting tap on the child element and not the screen.
Here is the code:
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: GestureDetector(
onTap: () => print('Tapped'),
child: QQBody(),
),
),
);
}
}
class QQBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
'Hello, World!'
),
);
}
}
Output: "tapped" printed when I click "Hello, World!".
Expected Output: "tapped" printed when I click anywhere on the screen with Text in the Center.
How do I do this?
Use GestureDetector's behavior property and pass HitTestBehavior.opaque to it, which recognizes entire screen and detects the tap when you tap anywhere on the screen.
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
child: QQBody(),
),
Hope this answers your question.
Like Darshan said, you can trigger Tap by wrapping the body inside a Gesture detector like this...
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
child: QQBody(),
),
Also in some cases, you may need to avoid trigger clicking on other widgets while tapping anywhere in body. That can be solved by using IgnorePointer Widget
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('This will click');
},
// -------- No Widget below tree will be trigger click.
child: IgnorePointer(
ignoring: ClassLibrary.selected != null ? true : false,
child: TabBarView(
children: [
...
#Darshan is a correct answer but I want to add something to it. I wanted a trigger whenever the screen is touched and onTap only reacts to a tap (no slide). However that behaviour can be achieved bij adding onTapCancel
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => print('Tapped'),
onTapCancel: () => print('Touched but not tapped'),
child: QQBody(),
),
Please use below code--
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: GestureDetector(
onTap: () => print('Tapped'),
child: Container(
height : MediaQuery.of(context).size.height,
width : MediaQuery.of(context).size.width,
),
),
),
);
}
}
You can use Stack: put layers on top of each other.
The good thing about Stack is you can arrange stack order as you want.
If you want to do something as the screen is touched, and do something else if a button on the screen is touched, you can easily do it with stack by putting the button on top of GestureDetector.
The following is your example:
class QQHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('QuoteQuota'),
),
body: Stack(
// Bottom to Top order
children: <Widget> [
QQBody(),
GestureDetector(
onTap: () => print('Tapped'),
),
]
),
),
);
}
}
class QQBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
'Hello, World!'
),
);
}
}

Difference in navigation transition in Flutter compared to "stock" Android

Compare the following transitions when navigating between routes.
Flutter: https://imgur.com/ZGspVO3
Android: https://imgur.com/piil7BO
You can see one clear difference with the transitions. The pages are pulled over and back on each other when navigated between them. The style I prefer is this one and not the one that Flutter uses.
Is this a bug or has this transition been changed deliberately?
I use the following code to navigate:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigation Transition'),
),
body: Center(
child: RaisedButton(
child: Text('Navigate'),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text('Navigation Transition'),
),
body: Center(
child: RaisedButton(
child: Text('Navigate'),
onPressed: () => Navigator.pop(context),
),
),
),
),
),
),
),
);
}
Modifying the apps theme data as follows solved the problem:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigation transitions',
theme: ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: const {
TargetPlatform.android: OpenUpwardsPageTransitionsBuilder(),
},
),
),
....
}
More information regarding the transitions during route changes:
MaterialPageRoute.class Documentation
PageTransitionsTheme.class Documentation
Navigator.class Documentation

Show Flutter's SnackBar above a visible Drawer menu?

I have a Scaffold with a simple Drawer in which I show a menu where a user can press a button. When this button is pressed I want to display a SnackBar, but the SnackBar is always displayed behind the Drawer. Is there some way to show it in front of the Drawer?
The drawer's code looks like:
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.lock_open),
title: Text('Click Me'),
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
'Test.',
)));
},
),
],
),
);
}
}
and it is used directly in the Scaffold:
return Scaffold(
drawer: MyDrawer(),
[...]
They said Drawer must be on the top of UI under MaterialDesign rules. But if you wanna such behaviour too much you can write own SnackBar.
static void showSnackBarAsBottomSheet(BuildContext context, String message)
{
showModalBottomSheet<void>(
context: context,
barrierColor: const Color.fromRGBO(0, 0, 0, 0),
builder: (BuildContext context) {
Future.delayed(const Duration(seconds: 5), () {
try {
Navigator.pop(context);
} on Exception {}
});
return Container(
color: Colors.grey.shade800,
padding: const EdgeInsets.all(12),
child: Wrap(children: [
Text(
message,
style:
GoogleFonts.robotoCondensed().copyWith(color: Colors.white),
)
]));
},
);
}
I've solved it by adding one more Scaffold inside Drawer and making its background transparent:
return Scaffold(
drawer: Scaffold(
backgroundColor: Colors.transparent,
body: MyDrawer(),
[...]