Flutter Access child function from its grand parent - flutter

In my e-commerce admin cpanel app
I have a very big form (product form) with lots of section one for title and description and price, one for sale one for colors and seizes and images etc.
I divided it into smaller widget in separated classes and each of them has its own children widgets in another separated classes
each child class has a ( onSave ) function which I need to trigger from the main form button located in the grand grand parent so how can I access all ( onSave ) functions from this parent
In this ( onSave ) functions I'm using provider pattern to pass the data I collect in each widget to a provider class and in there I can send the datd to the server

You can use the Form() widget and place your input fields in it as children. then you define a GlobalKey() and pass it to the form's key property. once you call the save method on the GlobalKey, all children widgets onSaved function will be called.
e.g.
import 'package:flutter/material.dart';
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: "Name"),
onSaved: (name) {
// do stuff
},
),
TextFormField(
decoration: InputDecoration(labelText: "Category"),
onSaved: (category) {
// do stuff
},
),
],
)),
ElevatedButton(
onPressed: () {
_formKey.currentState.save();
},
child: Text("pressme"),
)
],
),
),
);
}
}

Related

Too many postival arguments when adding hero transition, image, and text

import 'package:flutter/material.dart';
class DetailsScreen extends StatefulWidget {
final int index;
const DetailsScreen({Key? key, required this.index}) : super(key: key);
#override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Hero(
tag: widget.index,
child: Image.network(
"https://raw.githubusercontent.com/markknguyen/pictures/master/pic/${widget.index + 1}.png",
),
const Text("Rome"),
),
),
);
}
}
I tried adding const thinking it will resolve the issue but I didn't. The code did not run. I Just wanted to add some sort of text box in a page. const Text("Rome"), is the main concern.
You can't just have your Text widget there with no parent. You need to put your Hero and Text widget in a Column like so:
Center(
child: Column(
children: [
Hero(
tag: widget.index,
child: Image.network(
"https://raw.githubusercontent.com/markknguyen/pictures/master/pic/${widget.index + 1}.png",
),
),
Text("Rome"),
],
),
),
or any other Widget that acceptes multiple children such as Row or ListView based on your needs
The issue is that your Text() widget isn't passed as a parameter. Currently, the code can't compile due to treating your Text("Rome") as a 'mistake', sort of speaking.
Depending on your use-case scenario, you can either use:
a Column() - if you want your widgets to be one after another in a column.
a Stack() - if you want your widgets to be placed one under another.
You'll have to pass the children attribute to both, so for example:
Column(
children: [
Image.network(
"https://raw.githubusercontent.com/markknguyen/pictures/master/pic/${widget.index + 1}.png",
),
const Text("Rome"),
]
)

flutter: BehaviorSubject not rebuild widget

I am trying to use Rxdart on my statless widget:
class SimplePrioritySelectWidget extends StatelessWidget {
BehaviorSubject<List<String>> _valueNotifier =
BehaviorSubject<List<String>>.seeded([]);
I wrap my widget by StreamBuilder:
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _valueNotifier.stream,
initialData: options,
builder: (context, snapshot) {
print("rebuild");
return Padding(
padding: const EdgeInsets.only(top: 25),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 16.h,
),
I have a custom drop down widget, I don't know why, when I add a string inside _valueNotifier the builder method not called? and my widget not rebuilded? What is wrong?
CustomDropdown(
dropdownMenuItemList: options,
enableBorderColor: Color(PRIMARY_COLOR_2),
onChanged: (value) {
_valueNotifier.value.add(value);
},
),
I totally agree that the
you need to use sink in _valueNotifier
CustomDropdown(
dropdownMenuItemList: options,
enableBorderColor: Color(PRIMARY_COLOR_2),
onChanged: (value) {
_valueNotifier.sink.add([value]);
},
),
Mutating the value won't notify BehaviorSubject of anything. In order to make BehaviorSubject notify its listeners, you need to provide a different state object.
CustomDropdown(
dropdownMenuItemList: options,
enableBorderColor: Color(PRIMARY_COLOR_2),
onChanged: (value) {
Sta
_valueNotifier.value = [..._valueNotifier.value, value];
// or _valueNotifier.add([..._valueNotifier.value, value]);
},
),
Also, a BehaviorSubject is state and should not be created in a StatelessWidget. If you try anyway, the subject will be created (with the same initial value) every time your widget is rebuilt.

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
}
}

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

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>();

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.