I created a function below
nextQuestion() {
setState(() {
questionIndex++;
});
print('pressed');
}
I then created the class below and passed in the method above a constructor for an onPressed property
class Answer extends StatelessWidget {
final String answer;
final VoidCallback? selectHandler;
// ignore: use_key_in_widget_constructors
const Answer({required this.answer, this.selectHandler});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: selectHandler,
child: SizedBox(
width: double.infinity,
child: Center(
child: Text(
answer,
),
),
),
);
}
}
I then used the constructor of the class and passed in the function to update the text on my screen which is in a list with questionIndex as its index
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Question(question: questions[questionIndex]),
Answer(selectHandler: nextQuestion(), answer: 'Yellow'),
Answer(selectHandler: nextQuestion(), answer: 'Yellow'),
Answer(selectHandler: nextQuestion(), answer: 'Yellow'),
],
),
This is not working/Is not updating the text on my screen after pressing the button
I've recreated the code and it works for me with a few exceptions:
Answer(selectHandler: nextQuestion(), answer: 'Yellow'), is wrong. You have to use selectHandler: nextQuestion (no () after the function)
Use the answer widget inside another stateful widget.
If you did all these and it still doesn't work run flutter clean followed by flutter pub get (close the debug session first)
And if that doesn't work, the problem is with your questions widget.
Related
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
}
}
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>();
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.
I'm new to flutter,
So I'm working on a Flutter app & I Have My Functions & Widget File named: 'Function' Separated from my Main but I'm trying to set state in 'Main' from 'Function'
I have tried to set a global variable inside a MyText class Widget inside 'Function' and import 'Main'==> Function & Vice Versa at the same time, but I can't seem to manipulate the GlobalKey variable which would trigger setState again
(Class & MyText class Has since been Scrapped)
I also tried to set the function from my other file to the button like so
floatingActionButton: functions.random()
And somehow was able to set state (Sorry I Forgot How), but it kept running without being pressed
'Main.dart' Sample Code
String display = "";
class _MyHomePageState extends State<MyHomePage> {
var currentScreen = display;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AutoSizeText(
currentScreen,
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: functions.menu(),
);
} //Build
}// _MyHomePageState
'Function.dart' Sample Code
SpeedDial menu(){
return SpeedDial(
SpeedDialChild(
child: Icon(Icons.autorenew),
backgroundColor: Colors.lightBlueAccent,
label: 'New Activity',
labelStyle: TextStyle(fontSize: 18.0),
onTap: () => random(),
),
)
random(){
...
return someString;
}
Intended Result
When Child 'New Activity' is clicked, setState is called with the variable currentScreen's state being set to the result from the function 'random()'
Thank You In Advance!
I don't know if this can finish your problem, but the approach will be a little different from your approach because I'm not really familiar with the globalsetkey works...
so from my perspective I'm using the bloc pattern so hope this can give you some inspiration how to resolve this problems
in classBLoc.dart
BehaviorSubject<bool> _dialClicked = BehaviorSubject<bool>();
Observable<bool> get dialClicked => _dialClicked.stream;
Function(bool) get dialClickedListener => _dialClicked.sink.add;
in main.dart
section floatingActionButton,
floatingActionButton: functions.menu(currentScreen),
in Function.dart
Widget menu(){
final bloc = Provider.of<classBLoc>();
return StreamBuilder(stream: bloc.dialClicked, initialData: false,builder: (context, snapshot){
return SpeedDial(
SpeedDialChild(
child: Icon(Icons.autorenew),
backgroundColor: Colors.lightBlueAccent,
label: 'New Activity',
labelStyle: TextStyle(fontSize: 18.0),
onTap: () => snapshot.data ? currentScreen : random(), // this used for checking data if have already been clicked or not
);
}),
);
Hope this can you some inspiration how it's worked //sorry if there is wrong bracketed
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.