I have a TemplateView page that has a content parameter that hosts content that changes depending on my application.
Each content has a specific provider.
On the other hand, my TemplateView page has a button which calls a validation function common to each provider.
Here the example of my app (in green my TemplateView, in red the content who change):
Here is a simplified code of Template View. We see the call to the content and the validation button which calls the provider of the content ContentView1.
class TemplateView extends StatelessWidget{
final String title;
final StatelessWidget content;
TemplateView ({
Key? key,
required this.title,
required this.content,
required this.validationMessage,
}) : super(key: key);
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body : _buildBody(context),
),
),
);
}
Widget _buildBody(BuildContext context)
{
// Here the call of my provider for the ContentView1
var _messageProvider = Provider.of<ContentView1Provider>(context);
return Column(
children: [
SingleChildScrollView(
child: Container(
child: content,
),
),
InkWell(
child: Container(
child: Text('SAVE'),
),
onTap: () => _messageProvider.validation()
),
],
);
}
}
And here, how I call the TemplateView in my router:
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: ContentView1(),
),
)
);
All are working, now like I said the contents will change but my TemplateView is common. I therefore cannot enter in the TemplateView the call to the provider directly since it will change depending on the pages.
So I want to make the call to the provider in the TemplateView settings but it doesn't work.
My new TemplateView:
class TemplateView extends StatelessWidget{
final String title;
final StatelessWidget content;
final Function validationMessage; // => I added this line
TemplateView({
Key? key,
required this.title,
required this.content,
required this.validationMessage, // => I added this line
}) : super(key: key);
#override
Widget build(BuildContext context)
{
// => I remove the call of the provider line
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body : _buildBody(context),
),
),
);
}
Widget _buildBody(BuildContext context)
{
return Column(
children: [
SingleChildScrollView(
child: Container(
child: content,
),
),
InkWell(
child: Container(
child: Text('SAVE'),
),
onTap: () => validationMessage() // => I changed this line
),
],
);
}
}
My new router :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: Content1View(),
validationMessage: () => Provider.of<ContentView1Provider>(context).validation(),
),
)
);
It doesn't work, how to do this ?
EDIT with the solution
I added Consumer in my router :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
title: "Content 1 page",
message: Consumer<ContentView1Provider>(builder :(ctx , provider , child){
return ContentView1();
}),
validationMessage: () => Provider.of<ContentView1Provider>(context).validation(),
),
)
);
I am not sure if I understand this case well, but I just tell an Idea if it is work and if I understood what you ask :
in your route pass provider to the Tamplate page :
case RouterName.kContentView1:
return CupertinoPageRoute(
builder: (context) => ChangeNotifierProvider<ContentView1Provider>(
create: (BuildContext context) => ContentView1Provider(),
child: TemplateView(
provider : ContentView1Provider , // add this line
title: "Content 1 page",
message: Content1View(),
validationMessage: () => Provider.of<ContentView1Provider>(context).saveMessage(),
),
)
);
in template view recieve this provider :
class TemplateView extends StatelessWidget{
final provider; // add this line
final String title;
final StatelessWidget content;
final Function validationMessage; // => I added this line
TemplateView({
Key? key,
required this.provider, // add this line
required this.title,
required this.content,
required this.validationMessage, // => I added this line
}) : super(key: key);
now You can use Consumer with provider you recieved for each content :
return Consumer<provider>(builder :(ctx , provider , child){
return //what you want ....;
})
May I have missunderstood
Related
Good day, I need help adding a second child to the body of my screen. I keep on getting the error "The argument for the named parameter 'children' was already specified." If I take that piece of code out, my 'app' works perfectly. I've tried adding Column to my body (saw it in a different question) but it still gives me the error.
The problematic code is
,children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
Full Code:
class DashBoard extends StatelessWidget {
const DashBoard({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenUno(title: 'Feature Screen uno');
}));
},
child: const Text('Feature Screen uno')
)
]
,children: [TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
}));
}
,child: const Text('Feature Screen dos'),
),
]
)
);
}
}`
adding multiple children to Column or Row or any other multi child widget is not like that,
you would have a single Column with a list of children inside it
Column(
children: [
const Text('Child 1'),
const Text('Child 2'),
...
]
)
please refer to Column widget to know more about column, same work goes to row & some other multi child widgets
class DashBoard extends StatelessWidget {
const DashBoard({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
///child one
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenUno(title: 'Feature Screen uno');
}));
},
child: const Text('Feature Screen uno')
),
///child two
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return const FeatureScreenDos(title: 'Feature Screen dos');
}));
}
,child: const Text('Feature Screen dos'),
),
]
)
);
}
}`
body will always take one child we customize further ourselfs by adding column and rows for example here you can add a row as well in children if you want to show something horizontal after text buttons and further add more children to rows
When I pass the value of userRole from ( Profile screen to Cancel screen ). I am not able to return value using model route. It sends a null value. Please help.
my profile.dart file
Widget displayProfile(BuildContext context, DataSnapshot dataValues) {
final values = dataValues.value;
final userRole = values['role'];
return Padding(
padding: const EdgeInsets.all(
AppPadding.p16,
),
child: Column(
children: [
ListTile(
title: Text(
AppString.cancelledOrder,
style: themeData.textTheme.headline4,
),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).pushNamed(
PageRoutes.cancelOrderScreen,
arguments: userRole);
},
),
],
}
cancelOrder.dart file where the argument is passed
class CancelOrderScreen extends StatefulWidget {
const CancelOrderScreen({Key? key}) : super(key: key);
#override
State<CancelOrderScreen> createState() => _CancelOrderScreenState();
}
class _CancelOrderScreenState extends State<CancelOrderScreen> {
#override
Widget build(BuildContext context) {
final userRole = ModalRoute.of(context)!.settings.arguments as dynamic;
return Scaffold(
appBar: AppBar(
title: const Text(userRole),
),
),
}
my separate route.dart file
class RouteGenerator {
static Route<dynamic> getRoute(RouteSettings routeSettings) {
switch (routeSettings.name) {
case PageRoutes.profile:
return MaterialPageRoute(builder: (_) => const Profile());
case PageRoutes.cancelOrderScreen:
return MaterialPageRoute(builder: (_) => const CancelOrderScreen());
default:
return unDefinedRoute();
}
}
I want to validate the user input and then enable the Dialog's Submit action.
Using StatefulBuilder inside the dialog's content doesn't work here, since the actions does not live in the content Widget. So how would one go about changing a property on an action widget?
Currently my workaround is to not use actions, and have a Row of buttons inside the dialog's content, which is OK, but I'd like to know whether there is a better way.
I feel that using a Provider just to hold the "validation result" is a bit overkill.
EDIT: Example demonstrating that a StatefulBuilder allows only inner widgets to be updted with state changes. The button sets enabled, but the of two "action buttons" only the one inside the StatefulBuilder is rebuilt. The AlertDialog actions does not get updated.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dialogs'),
),
body: Center(
child: TextButton(
child: Text('Clickme'),
onPressed: _dialogWithAction,
),
),
);
}
Future<void> _dialogWithAction() async {
showDialog<bool>(
context: context,
builder: (BuildContext dialogContext) {
bool enabled = false;
return AlertDialog(
title: Text('Add Address'),
content: StatefulBuilder(builder: (context, innerSetState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
child: Text('Enable Submit'),
onPressed: () {
enabled = true;
innerSetState(() {});
},
),
Row(
children: [
// This Workaround widget is rebuild by the stateSetter
TextButton(
onPressed: enabled ? () => Navigator.pop(context) : null,
child: Text('Submit'),
)
],
)
],
);
}),
actions: <Widget>[
// This Widget does not get rebuilt when "enabled" is changed
TextButton(
child: Text('Submit'),
onPressed: enabled
? () {
Navigator.of(dialogContext).pop();
}
: null,
),
],
);
},
);
}
}
I have a loop index that is creating ~20 ListTiles that tap to a second screen that reference its index. However it looks like it's passing by reference since the value is always the same on the second screen
user defined upper_bound
...
for(int i=0; i<upper_bound;i++)
{
...
Container -> ListTile ->
title: GestureDetector(
onTap: () async {
var returnData = await Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
SecondScreen(
index: i,
))
);}
}
In this situation, the second screen always receives index as upper_bound and not the value I'd expect which is the value at the time of the loop. How can I pass the current value of the index?
in the first page/screen
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListTile Example"),
),
body: new ListView(
children: new List.generate(20, (int index) {
return new ListTile(
onTap: () {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) => NextPage(
number: index,
),
),
);
},
title: new Text(
"Index No #$index",
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 25.0),
),
subtitle: new Text("My subtitle is"),
);
}),
),
);
}
in the next or second page
import 'package:flutter/material.dart';
class NextPage extends StatefulWidget {
final int number;
NextPage({
Key key,
#required this.number,
}) : super(key: key);
#override
_NextPageState createState() => _NextPageState();
}
class _NextPageState extends State<NextPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(widget.number.toString()),
),
);
}
}
I'm creating a custom AppBar that I can use across multiple pages. customappbar.dart
class CustomAppBar extends AppBar {
CustomAppBar({Key key, Widget title})
: super(
key: key,
title: title,
actions: <Widget>[
IconButton(
icon: Icon(Icons.directions_car),
onPressed: () {
Navigator.push(
context, //UNDEFINED
MaterialPageRoute(
builder: (context) => NewPage(),
),
);
},
),
],
);
}
Here's an example of where it's going to go. newpage.dart
class _ NewPageState extends State<NewPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CashOnHandAppBar(
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[],
),
),
);
}
}
context is getting of undefined. is the build method missing?
You are extending AppBar class it will not work for custom appbar you have to extend StatelessWidget and implement PreferredSizeWidget class for custom appbar
Here is the working example of custom appbar :-
import 'package:flutter/material.dart';
class SimpleAppBar extends StatelessWidget implements PreferredSizeWidget {
final String _title;
final bool centerTitle;
SimpleAppBar(this._title, {this.centerTitle = false, Key key})
: preferredSize = Size.fromHeight(56.0),
super(key: key);
#override
final Size preferredSize; // default is 56.0
#override
Widget build(BuildContext context) {
return AppBar(
title: Text(_title),
centerTitle: centerTitle,
);
}
}
And instead of adding function directly into custom appbar pass function reference to the class constructor
Need to pass below function add reference :-
onPressed: () {
Navigator.push(
context, //UNDEFINED
MaterialPageRoute(
builder: (context) => NewPage(),
),