Hello im struggling to get the new way of showing a Snackbar to display over a bottomsheet.
With the below code the Snackbar shows over the top of the bottomsheet but also under the bottom sheet.
Maybe this is expected behaviour?
When I dismiss the bottom sheet, the snackbar disappears with it, but reveals another snackbar under it
My code:
onTap: () {
showTaskInfo(context, taskDetails.taskID);
},
//show bottom sheet - (parent is Scaffold)
Future<dynamic> showTaskInfo(BuildContext context, int taskID) {
final GlobalKey<ScaffoldState> modelScaffoldKey = GlobalKey<ScaffoldState>();
return showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Scaffold(key: modelScaffoldKey, body: TaskInfo(taskID, modelScaffoldKey));
});
}
//TaskInfo is a RiverPod state Widget
class TaskInfoState extends ConsumerState<TaskInfo> {
#override
Widget build(BuildContext context) {
showSnackBar(context, widget.modelScaffoldKey)
}
}
void showSnackBar(BuildContext context, GlobalKey<ScaffoldState> modelScaffoldKey) {
//THIS WORKS! But is depreciated
//modelScaffoldKey.currentState!.showSnackBar(snackBarDetails);
//This shows a snackbar on the app main scaffold and the scaffold for bottomsheet scaffold
ScaffoldMessenger.of(context).showSnackBar(snackBarDetails);
}
As the comments suggest
Using a key still works as expected.
But using scaffoldMessenger with context shows the snack bar below the bottomsheet and on top
I think this is a context issue, Pretty sure I should be using the context from the scaffold.. which I think I am??
Related
`` I am making an app which should be able to open multiple tabs. I am saving tabs as widgets in Map<Item, Widget>, where Item represents tab info, and the widget is a tab widget. When Navigator is called to show specific tab, it searches in map by item and returns tab widget. If there is no such tab, it creates new tab widget, saves it in map and returns tab. My tab widget is using AutomaticKeepAliveClientMixin with wantKeepAlive => true and super.build(context), it is all ok in widget code.
The problem is that state is not saving. I debugged it and figured out, that when widget is firstly created and super.build(context) is called, it does not call _ensureKeepAlive because the condition is not met (code from super.build):
#mustCallSuper
#override
Widget build(BuildContext context) {
if (wantKeepAlive && _keepAliveHandle == null) {
_ensureKeepAlive();
}
return const _NullWidget();
}
}
wantKeepAlive is true in this condition, but _keepAliveHandle somewhy is not null already, so _ensureKeepAlive() is not running.
When I open this tab later, _ensureKeepAlive() is not called for the same reason, and then my widget builder is being executed, which rebuilds widget from scratch. So _ensureKeepAlive() will be never called. What's wrong with my code?
Here is a part of my code where Navigator is called:
final Map<Item, Widget> pages = {};
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
Item pageId = (settings.arguments ??
Item(type: ItemTypes.boardList, tag: "", name: "Doski"))
as Item;
Widget? page = pages[pageId];
// if page with that id not exist yet, then create it
if (page == null) {
page = BoardListScreen(
key: ValueKey(pageId),
title: "Doski",
pages[pageId] = page;
}
}
return MaterialPageRoute(builder: (context) {
return pages[pageId]!;
});
}),
drawer: Drawer(...));
}
I tried to put a unique key to a new widget, nothing has changed.
Is it possible to use something like the scaffoldMessenger in a GetX Controller?
I'd like to display a SnackBar via the scaffoldMessenger when a task is complete.
For example, I have a controller that uploads data to Firestore, and I'd like to show the SnackBar when this is complete:
final ScaffoldMessengerState scaffoldMessenger = Get.find<ScaffoldMessengerState>();
Future<void> startUpload(SaveGame saveGame) async {
final UploadTask taskSnapshot = backupService.uploadFile(saveGame);
await taskSnapshot
.whenComplete(() =>
scaffoldMessenger.showSnackBar(
const SnackBar(content: Text('Snack-tastic')),
));
}
I tried wiring that into my main.dart
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
final ScaffoldMessengerState scaffoldMessenger =
Get.put<ScaffoldMessengerState>(ScaffoldMessenger.of(context));
return GetMaterialApp(...)
}
}
But that just threw an exception
No ScaffoldMessenger widget found.
Typically, the ScaffoldMessenger widget is introduced by the MaterialApp at the top of your application widget tree.
I'm aware of the GetX GetBar and SnackBar alternative.
How would you typically solve this, would the pattern by to have my startUpload return and then in the 'screen/page' have the logic to display the snackbar where it has access to the BuildContext?
You need to wrap using the Builder to use the context of the MaterialApp like below.
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: Builder(builder: (context) {
Get.put<ScaffoldMessengerState>(ScaffoldMessenger.of(context));
return const TestPage();
}),
);
}
So I'm having an issue with a bottom sheet that I'm trying to display.
the idea is that i want to display a bottom sheet and remove my bottom navigation bar when the bottom sheet shows. anyway, I've made a boolean called sheetOpen which is set to false initially and the idea is to set it to true in order to close the bottom navigation bar when the sheet pops up .
doing so without using setstate does not reflect any changes to the UI . But if I use set state in the show Bottom sheet function the app crashes and i get this message : 'Looking up deactivated widget's ancestor is unsafe. at this point the state of the widget's element tree is no longer stable . to safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.'
I have tried multiple solutions(Stateful Builder, calling _controller.setstate ..) but nothing works.
Been stuck at this for 3 days..
anyway i will show the code that i have written and i would really appreciate anyone who can help.
class FeedScreen extends StatefulWidget {
static bool sheetOpen = false;
static int selectedIndex = 0;
const FeedScreen();
#override
_FeedScreenState createState() => _FeedScreenState();
}
class _FeedScreenState extends State<FeedScreen> {
late final GlobalKey<ScaffoldState> _key;
late PersistentBottomSheetController _controller;
void _showPreview(
final BuildContext context,
) {
//this is what's causing the issue
setState(() {
FeedScreen.sheetOpen = true;
});
_controller = _key.currentState!.showBottomSheet(
(ctx) {
//etc....
}
#override
void initState() {
super.initState();
_key = GlobalKey<ScaffoldState>();
}
Widget build(BuildContext context) {
return Scaffold(
//....
bottomNavigationBar: FeedScreen.sheetOpen
? null
: BottomNavBar(
FeedScreen.selectedIndex,
_changeTab,
),
),
https://api.flutter.dev/flutter/widgets/StatefulBuilder-class.html?
you can wrap Stateful builder with Bottomsheet to use setState.
wrap it with statefulbuilder
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return BottomNavBar(
FeedScreen.selectedIndex,
_changeTab,
);}
I have a screen that pop all routes and push a new one. At this new screen that is being pushed, I want to show a snackbar, but I'm not being able to make it work.
There is also another part of the application that I'll need to do something similar, but not popping all routes.
This is how I'm currently trying, but I believe it's not working because I'm not using the context of the new pushed screen:
if (something) {
Navigator.pushNamedAndRemoveUntil(
context,
searchRoute,
(_) => false,
);
final snackbar = SnackBar(
content: Text("Thing done successfully!"),
backgroundColor: Colors.green[700],
);
Scaffold.of(context).showSnackBar(snackbar);
return true;
} else {
return false;
}
That's why I wanna know if there's a way to get the context of the route when I push it with the Navigator, so I can then use it to show the snackbar correctly.
You must use GlobalKey, declare _scaffoldKey in class like,
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
then setup key in your Scaffold
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
later use,
_scaffoldKey.currentState.showSnackBar(snackbar);
Hope it works.
I have a widget called RootContainer which receives a Widget child and wrap it inside a StreamBuilder:
class RootContainer extends StatelessWidget {
final Widget child;
RootContainer({this.child});
#override
Widget build(BuildContext context) {
return StreamBuilder<OverlayAlert>(
stream: ApiService.ThrottledException.stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
Future.delayed(Duration.zero, () => showAlert(context, snapshot));
}
return this.child;
},
);
}
void showAlert(BuildContext context, snapshot) {
print("showing dialog");
showDialog(
context: context,
builder: (context) => OverlayAlertDialog(snapshot),
);
}
When an error occurs, I add a new value to the stream ApiService.exception.stream which triggers the StreamBuilder builder and then it opens a dialog.
This is the current widget tree:
The problem starts when I want to pop the navigator, the StreamBuilder.builder builds again!
I thought it may happen because the RootContainer is being rebuilt, but placing a print before the StreamBuilder has resulted in just one print.
I tried to .listen to the stream, and the stream didn't fire when I popped the navigator so I can confirm that there's nothing wrong with ApiService.ThrottledException.stream.
The snapshot when the navigator is popped is equal (the data) to the last emission.
You can see in the following demo that whenever I press the back button the dialog pops up again:
What could cause the StreamBuilder to rebuild itself when I press on the back button?
I had to change RootContainer to extend StatefulWidget instead of StatelessWidget.
I have no idea what's happening behind the scene, but it works! Any explanation would be nice.