Flutter : using changeNotifier and provider when the context is not available - flutter

I'm trying to use the simple state management described in the Flutter docs, using a ChangeNotifier, a Consumer, and a ChangeNotifierProvider.
My problem is that I can't get a hold a on valid context to update my model (details below...). I get an error:
Error: Error: Could not find the correct Provider above this CreateOrganizationDialog Widget
This likely happens because you used a BuildContext that does not include the provider of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that CreateOrganizationDialog is under your MultiProvider/Provider.
This usually happen when you are creating a provider and trying to read it immediately.
Here are extracts of my code:
class OrganizationModel extends ChangeNotifier {
final List<Organization> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Organization> get items => UnmodifiableListView(_items);
void addList(List<Organization> items) {
_items.addAll(items);
notifyListeners();
}
}
This is my model.
class OrganizationBodyLayout extends StatelessWidget {
Future<void> _showCreateOrganizationDialog() async {
return showDialog<void>(
context: navigatorKey.currentState.overlay.context,
barrierDismissible: false,
child: CreateOrganizationDialog());
}
_onCreateOrganizationPressed() {
_showCreateOrganizationDialog();
}
_onDeleteOrganizationPressed() {
//TODO something
}
_onEditOrganizationPressed() {
//TODO something
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(mainAxisSize: MainAxisSize.max, children: [
ButtonBar(
alignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
RaisedButton(
onPressed: _onCreateOrganizationPressed,
child: Text("New Organization"),
),
],
),
Expanded(
child: Container(
color: Colors.pink,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: ChangeNotifierProvider(
create: (context) => OrganizationModel(),
child: OrganizationListView(),
)),
Expanded(child: Container(color: Colors.brown))
]))),
]));
}
}
A stateless widget that contains a ChangeNotifierProvider just on top of the list widget using the model.
On a button click, a modal dialog is shown, then data is fetched from the network. I should then update my model calling the addList operation.
Below is the code for the stateful dialog box.
class CreateOrganizationDialog extends StatefulWidget {
#override
_CreateOrganizationDialogState createState() =>
_CreateOrganizationDialogState();
}
class _CreateOrganizationDialogState extends State<CreateOrganizationDialog> {
TextEditingController _nametextController;
TextEditingController _descriptionTextController;
#override
initState() {
_nametextController = new TextEditingController();
_descriptionTextController = new TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 200,
height: 220,
child: Column(
children: [
Text('New organization',
style: Theme.of(context).textTheme.headline6),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration: new InputDecoration(hintText: "Organization name"),
controller: _nametextController,
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
decoration:
new InputDecoration(hintText: "Organization description"),
controller: _descriptionTextController,
),
),
ButtonBar(
children: [
FlatButton(
child: new Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: new Text("Create"),
onPressed: () {
setState(() {
Future<Organization> organization =
backendCreateOrganization(_nametextController.text,
_descriptionTextController.text);
organization.then((value) {
Future<List<Organization>> organizations =
backendReloadOrganizations();
organizations.then((value) {
var model = context.read<OrganizationModel>();
// var model = navigatorKey.currentState.overlay.context.read<OrganizationModel>();
//model.addList(value);
});
});
});
Navigator.of(context).pop();
//context is the one for the create dialog here
},
)
],
)
],
),
));
}
}
My problem happens at the line
var model = context.read<OrganizationModel>();
Thinking of it, the context available here is the modal dialog box context - so it's kind of logical that the Provider is not found in the widget tree.
However, I can't see how to retrieve the proper context (which would be the one for the result list view, where the Provider is located) in order to get the model and then update it.
Any idea is welcome :-)

Solved (kind of).
The only way I've found to solve this is by making my model a global variable:
var globalModel = OrganizationModel();
And referencing this global model in all widgets that consume it. I can't find a way to find the context of a stateless widget from within a callback in another stateful widget.
It works, but it's ugly. Still open to elegant solutions here :-)

Get_it seems to be elegant way of sharing models across the application. Please check the documentation for the different use cases they provide.
You could do something like the following
GetIt getIt = GetIt.instance;
getIt.registerSingleton<AppModel>(AppModelImplementation());
getIt.registerLazySingleton<RESTAPI>(() =>RestAPIImplementation());
And in other parts of your code, you could do something like
var myAppModel = getIt.get<AppModel>();

Related

Does watch(provider) update children of parent widget?

I'm trying to update both Pages with one UserInteraction, therefore trying to access the same Stream in both Pages, with the Riverpod library.
Now to explain it further. When I pass the Stream to the CustomerPage I'm able to get the data (the String Anton). and when I click on the Button that triggers the change in FireStore, the String gets updated to "Marco" in the ParentWidget, when I go back to it. But it doesn't change in the CustomerPage unless I reopen the Page via the RaisedButton in the ParentWidget.
But I want it to update after I click the Button on the CustomerPage.
I hope this makes it clearer.
class ParentWidget extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc = watch(streamProvider.stream);
return Container(
child: Column(
children: [
Text(doc.name), //Lets say the name is Anton,
RaisedButton(
child: Text(" road to CustomerPage"),
onPressed:(){
Navigator.of(context).pushNamed(RouteGenerator.customerPage, arguments: doc);
},), //RaisedButton
],), //Column
); //Container
}
}
class CustomerPage extends StatelessWidget{
Stream<DocumentSnapshot> docStream
CustomerPage({this.docStream});
Widget build(BuildContext context){
return Column(
children: [
Text(docStream.name) //Here is also Anton
RaisedButton(
child: Text("Change Name"),
onPressed: () {
context.read(streamProvider).changeName("Marco");
},), //RaisedButton
]
); //Column
}
}
On how I've understood so far is that, riverpod allows you to fetch the state of a provider, which basically is a value(?), that's why it's sufficient to just watch it in any Widget you want to access it's data from. There is no need anymore (just speaking for my case), to let Widgets pass the around in the App.
Down below is the solution which i believe to be right.
It also doesn't matter on how many times I call the provider. It's always going to be same Instance. For my case it means, that doc and doc2 are the same.
I hope this makes it clearer.
class ParentWidget extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc = watch(streamProvider.stream);
return Container(
child: Column(
children: [
Text(doc.name), //Lets say the name is Anton,
RaisedButton(
child: Text(" road to CustomerPage"),
onPressed:(){
Navigator.of(context).pushNamed(RouteGenerator.customerPage);
},), //RaisedButton
],), //Column
); //Container
}
}
class CustomerPage extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc2 = watch(streamProvider.stream);
return Column(
children: [
Text(doc2.name) //Here is also Anton
RaisedButton(
child: Text("Change Name"),
onPressed: () {
context.read(streamProvider).changeName("Marco");
},), //RaisedButton
]
); //Column
}
}

Dialog state does not change after restarting (closing and opening again) in flutter

I have defined one button inside one StatelessWidget (This will have the bloc creation logic and injecting using bloc provider, ), on click of the button i am showing a dialog and passing the bloc instance to it, as shown in the code.
//EsignBloc is defined here in parent statelessWidget. Defined i.e. creating the bloc instance and passing through the BlocProvider. Removed the code for simplicity
//This listener will be called when Button defined inside statelessWidget will be clicked. this is responsible for showing the dialog.
void _onClickHere(
BuildContext context,
) {
final dialog = Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
elevation: 0.0,
backgroundColor: Colors.transparent,
child: _GetSignUsingOtpView(),
);
showDialog(
context: context,
builder: (_) => BlocProvider<EsignBloc>(
create: (_) => BlocProvider.of<EsignBloc>(context), // passing already created bloc to dialog
child: WillPopScope(
onWillPop: () => Future.value(false),
child: dialog,
),
),
barrierDismissible: false,
);
}
Pasting some code of _GetSignUsingOtpView()
class _GetSignUsingOtpView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<EsignBloc, EsignState>(builder: (context, state) {
return Container(
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(
AppConstants.borderRadius,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () => _closeDialog(context),
child: Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8),
child: Icon(
Icons.cancel,
color: AppColor.primaryDark,
size: SizeConfig.safeBlockVertical * 2,
),
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PrimaryText(text: state.otp), // data does not change after closing and opeing dialog again
PrimaryText(text: state.remainingTime), // data does not change after closing and opeing dialog again
],
),
),
],
),
);
});
}
void _closeDialog(BuildContext context) {
Navigator.pop(context);
}
}
The problem that I am facing is whenever the dialog opens again after closing, it doesn't show the latest data from the bloc. The dialog just shows whatever previous data is in the bloc. Can someone point it out, where i am making the mistake?
The reason that your dialog is not showing new data is because you are creating a new instance of the bloc. Even though you say that you're not.
BlocProvider<EsignBloc>(
create: (_) => BlocProvider.of<EsignBloc>(context), // passing already created bloc to dialog
child: ...
In some cases, BlocProvider can be used to provide an existing cubit to a new portion of the widget tree. This will be most commonly used when an existing cubit needs to be made available to a new route. In this case, BlocProvider will not automatically close the cubit since it did not create it.
To not create a new instance, you need to use BlocProvider.value. You can read more about it here
BlocProvider.value(
value: BlocProvider.of<EsignBloc>(context),
child: ...,
);
Instead of creating a new instance, this will use the previous instance and provide it to the child widget and will NOT close the bloc when its done using it. This is handy for dialogs and navigation.

Pass data from child widget to the parent widget

I have a Dashboard Widget whose body is like this:
I want to pass data from the child widget DashboardGrid(Check at the end of the Code block) to this parent widget. How do I do it?
body: Column(
children: <Widget>[
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Categories",
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold)),
],
),
IconButton(
alignment: Alignment.topCenter,
icon: new Icon(Icons.search, color: Theme.of(context).hintColor,),
onPressed: () {},
)
],
),
),
SizedBox(
height: 40,
),
DashboardGrid(),
])
With callback.
Create a function inside DashboardGrid
class DashboardGrid extends StatelessWidget {
final Function(String) callback;
DashboardGrid({this.callback});
....
Then inside the column you will instantiate it with the function
[
...,
DashboardGrid(callback:(String value)=>print(value));
]
When you want to pass that data inside DashboardGrid, just call the function
void passTheData(String data) => callback(data);
example is with String but you can pass any data.
If your child widget lives under the same widget tree, you can take advantage of Notification to bubble up data.
First, create notifications for what you need. You can either create one notification or an abstract Notification with multiple concrete ones. For this example, I'll assume you want to handle different notifications.
abstract class MyNotification extends Notification {}
class SomethingHappened extends MyNotification {}
class NothingHappened extends MyNotification {}
Then you can handle all notifications for that type in the parent widget:
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: (notification) {
// Handle your notification
return true;
},
child: Container(),
);
}
}
Or pick individual ones:
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: (notification) {
if(notification is SomethingHappened){
// Handle
} else if(notification is NothingHappened){
// Handle
}
return true;
},
child: Container(),
);
}
To emit the notification from your child widget, you just need to call T().dispatch(context) where T is your Notification type. For example, SomethingHappened().dispatch(context);. That's it.
For fixing that, there are two ways. The first way is to create a GlobalKey(https://docs.flutter.io/flutter/widgets/GlobalKey-class.html) and pass it as a parameter to the child widget. And the second way is to create a global variable for the parent state and use it in the child state.

Could not find the correct Provider

I'm new to using flutter and i was trying to implement a covid 19 tracker of cases in the world and i got this error when creating a custom widget to display number of cases , can anyone help me figure out the solution to this exactly as i have been trying to fix it for hours now.
This is the exception caught by the widgets library:
Error: Could not find the correct Provider above this NewCasesCard Widget
To fix, please:
Ensure the Provider is an ancestor to this NewCasesCard Widget
Provide types to Provider
Provide types to Consumer
Provide types to Provider.of()
Ensure the correct context is being used.
If none of these solutions work, please file a bug at:
https://github.com/rrousselGit/provider/issues
Here is the code:
import 'package:stay_safe/Providers/AppBrain.dart';
import 'package:stay_safe/utils/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
class NewCasesCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return getCard(context);
}
Widget getCard(context){
if (Provider.of<AppBrain>(context).isloading2)
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SpinKitPumpingHeart(color: AppTheme().kcolors[0],),
),
);
else {
final countryInfo = Provider.of<AppBrain>(context).countryStats['countrydata'][0];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.5,
color: Colors.white70,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('New Cases Today',style: GoogleFonts.cabin(fontSize: 25),),
),
SizedBox(height: 8,),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
StatIcon(countryInfo['total_active_cases'], 'Active', AppTheme().kcolors[0]),
StatIcon(countryInfo['total_new_cases_today'], 'New Cases', AppTheme().kcolors[2]),
StatIcon(countryInfo['total_new_deaths_today'], 'Deaths', AppTheme().kcolors[1])
],),
)
],
),
),
);
}
}
}
Widget StatIcon(int count,String type,Color color){
return Column(
children: <Widget>[
Text(count.toString(),style: GoogleFonts.cabin(color: color,fontWeight: FontWeight.bold,fontSize: 20),),
Text(type,style: GoogleFonts.cabin(color: color,fontWeight: FontWeight.bold,fontSize: 20),),
],
);
}
The issue looks like you have created the AppBrain provider in a different part of your widget tree to NewCasesCard, so when NewCasesCard attempts to find the provider by searching the tree it cannot. You need to make sure that the AppBrain provider is above NewCasesCard somewhere in the widget tree. So for example in the screen that includes this widget you could have:
class NewCasesScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<AppBrain>(
create: (_) => AppBrain(),
child: Scaffold(
body: ListView(
children: [
NewCasesCard(),
...
]
),
),
);
}
Alternatively you could initialise the provider at the top of your app since, from the name, it looks like it is used throughout. For this you could utilise the MultiProvider and have:
(Example taken from the package readme)
void main {
runApp(MultiProvider(
providers: [
Provider<AppBrain>(create: (_) => AppBrain()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
],
child: MaterialApp(...),
));
}
This way you can be sure that the provider will always be found.

Flutter snackbar alternative or easier method than wrapping everything in Scaffold?

I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!
But I need to let the user know that the content was copied.
I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?
Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).
import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
class RowRule extends StatelessWidget {
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'],
style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'],
style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
// Scaffold.of(context).showSnackBar(SnackBar
// (content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(
vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
)),
)
],
),
),
));
}
}
The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.
You can use GlobalKey to make it work the way you want it.
Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
key: _key,
body: Column(
children: <Widget>[
Container(
color: Color.fromRGBO(52, 56, 245, 1),
height: 150,
alignment: Alignment.center,
child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 120,
itemBuilder: (context, index) {
return Container(
color: Colors.white,
margin: const EdgeInsets.all(4),
child: ListTile(
title: Text("Row #$index"),
onLongPress: () => _key.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
),
);
},
),
),
],
),
);
}
}
These is a simple plugin replacement for the Snackbar named "Flushbar".
You can get the plugin here - https://pub.dartlang.org/packages/flushbar
You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all.
Inside your onLongPressed in GestureDetectore you can do this.
onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
Flushbar(
message: "Copied !!",
duration: Duration(seconds: 3),
)..show(context);
}
This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.
There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.
Here is the final code (Your way)
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
onPressed: () => print("Handle button press here"),
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
),
),
)
],
),
),
),
),
);
}
}
I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.
I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.
So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
if (!ruleGroup['details'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: ListView(children: _buildChildren()),
);
}
}
I read your comments on all answers and here is my conslusion:
You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.
Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.
Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.