I have a following state full widget. I need to reuse it as it is by just changing two variables id and collectionName. Generally I would extract a widget, but in this case I am modifying variable firstName which wont let me extract the widget.
class IndividualSignupPage1 extends StatefulWidget {
static final id = 'idIndividualSignupPage1';
final collectionName = 'Individual';
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
class _IndividualSignupPage1State extends State<IndividualSignupPage1> {
String firstName;
DateTime birthDate;
final firestoreObj = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: GeneralAppBar(
appBar: AppBar(),
),
body: Container(
child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
TextField(
onChanged: (value) {
this.firstName = value;
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: Card(
child: ListTile(
title: Text(
this.birthDate == null
? 'Birthdate'
: '${this.birthDate.year}-${this.birthDate.month}-${this.birthDate.day}',
),
onTap: () {
DatePicker.showDatePicker(
context,
initialDateTime: this.birthDate,
onConfirm: (newDate, listOfIndexes) {
setState(() {
this.birthDate = newDate;
});
},
);
},
),
),
),
],
),
WFullWidthButton(
name: 'Save',
onPressedFunc: () async {
// save all info to firestore db
firestoreObj.collection(widget.collectionName).document('xyz').setData({
'firstName': this.firstName,
'birthDate': this.birthDate,
}, merge: true);
},
),
]),
),
);
}
}
Thanks
You can pass the arguments to the Class IndividualSignupPage1 and then use it in its corresponding state class _IndividualSignupPage1State with the property "widget." like,
// pass the arguments from another class.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
Let say you want to use id and collectionName in its corresponding state class _IndividualSignupPage1State you can access it using "widget" property like,
appBar: AppBar(title: Text(widget.id)),
**OR**
appBar: AppBar(title: Text(widget.collectionName)),
Note: you can only access the widget property inside functions/methods only.
Create IndividualSignupPage1 constructor and pass data with constructor arguments.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);
Related
I have a listview.builder in flutter and every item of the list has a dropdown now whenever I select one dropdown value of every dropdown changes. how can I fix this problem in flutter?
Ok, after spending a couple of hours on this and not finding a satisfactory answer (but a lot of hints) I worked it out.
I made a new StatefulWidget class that wraps the DropdownButton. It is instantiated with the List of items for the dropdown.
listview_dropdownbutton.dart
import 'package:flutter/material.dart';
class ListviewDropdownButton extends StatefulWidget {
final List<dynamic> sizes;
const ListviewDropdownButton({
Key? key,
required this.sizes,
}) : super(key: key);
#override
State<ListviewDropdownButton> createState() => _ListviewDropdownButton();
}
class _ListviewDropdownButton extends State<ListviewDropdownButton> {
List<dynamic>? _sizes;
String _currentSize = '';
#override
Widget build(BuildContext context) {
_sizes = _sizes ?? widget.sizes;
_currentSize = _currentSize != '' ? _currentSize : widget.sizes[0];
return DropdownButton<dynamic>(
value: _currentSize,
style: const TextStyle(
color: Colors.green,
),
items: _sizes!.map<DropdownMenuItem<dynamic>>((dynamic size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (dynamic size) {
if (_currentSize != size) {
setState(() {
_currentSize = size!;
});
}
},
);
}
}
In the parent widget, just include the class and use it where you'd put the DropdownButton.
Here's a working example.
main.dart
import 'package:flutter/material.dart';
import 'listview_dropdownbutton.dart';
void main() => runApp(const DropdownButtonApp());
class DropdownButtonApp extends StatelessWidget {
const DropdownButtonApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DropdownButton In ListView')),
body: Center(
child: DropdownButtonExample(),
),
),
);
}
}
class DropdownButtonExample extends StatelessWidget {
DropdownButtonExample({super.key});
final List<String> _items = <String>['Shirt', 'T-Shirt', 'Pants', 'Blouse', 'Coat'];
final List<String> _sizes = <String>['Small', 'Medium', 'Large', 'X-Large'];
String _currentSize = 'Small';
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (
BuildContext context,
int index,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_items[index]),
Row(
children: [
ListviewDropdownButton(
sizes: _sizes,
),
DropdownButton<String>(
value: _currentSize,
style: const TextStyle(
color: Colors.red,
),
items: _sizes.map<DropdownMenuItem<String>>((String size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (String? size) {
if (_currentSize != size) {
// setState(() {
_currentSize = size!;
// });
}
},
),
],
),
const Divider(
thickness: 2,
height: 2,
),
],
);
},
);
}
}
To illustrate it works, I put both the ListviewDropdownButton and a regular DropdownButton in the ListView.
I added String _currentSize = 'Small'; and the onChanged method to show the regular DropdownButton does not work. It never changes from "Small", which was my original problem.
I'm trying to understand how to use the GetX package in a Flutter application to get a reactive update in a Text widget when the value is changed in a TextFormField.
What is displayed in the Text widget is the property of an observable object. And it is that property that is updated from the TextFormField.
The value is correctly updated in the controller but not in the widget.
If I use a string variable directly, it does update correctly. But as soon as I'm using an object, it does not update anymore.
This is a really simple sample of my application, just to be sure that that basics are understood.
Here is my code:
class User {
String name = "";
}
class TestController extends GetxController {
TestController();
final user = User().obs;
}
class MyHomePage extends StatelessWidget {
final c = Get.put(TestController());
final String title;
MyHomePage({this.title});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
width: Get.width * 0.8,
height: Get.height * 0.8,
child: Column(
children: [
Obx(() => Text(c.user.value.name)),
TextFormField(
onChanged: (value) => c.user.value.name = value,
),
],
),
),
),
);
}
}
Many thanks for your help !
Ok I found the solution thanks to the CodeX youtuber.
To be able to update reactively the UI when you change the value of a property an object, even if this object is set as observable, you need to have the property observable as well.
So the correct code will look like this
class User {
final name = "".obs;
}
class TestController extends GetxController {
TestController();
final user = User().obs;
}
class MyHomePage extends StatelessWidget {
final c = Get.put(TestController());
final String title;
MyHomePage({this.title});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
width: Get.width * 0.8,
height: Get.height * 0.8,
child: Column(
children: [
Obx(() => Text(c.user.value.name.value)),
TextFormField(
onChanged: (value) => c.user.value.name.value = value,
),
],
),
),
),
);
}
}
Update Your onChanged() fn to this
Obx(() => Text(c.user.value.name)),
TextFormField(onChanged: (value) {
c.user.value.name = value;
c.user.refresh();
}),
In sample we pass the single model like this
return ScopedModel<CounterModel>(
model: CounterModel(), // only one model here
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
);
How to pass some models instead of single model? So that to use them later this way
Widget build(BuildContext context) {
final username =
ScopedModel.of<UserModel>(context, rebuildOnChange: true).username;
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
return Text(...);
}
You can copy paste and run the full code below.
You can reference this https://newcodingera.com/scoped-model-in-flutter/
You can pass your three models and wrap them nested
Code snippet
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
Working demo
Full code
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
),
),
);
}
}
// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
// First, increment the counter
_counter++;
// Then notify all the listeners.
notifyListeners();
}
}
class UserModel extends Model {
String _username;
UserModel(String username) : _username = username;
String get username => _username;
set username(String newName) {
_username = newName;
notifyListeners();
}
}
class DataModel extends Model {
String _data;
DataModel(String data) : _data = data;
String get data => _data;
set data(String newData) {
_data = newData;
notifyListeners();
}
}
class CounterHome extends StatelessWidget {
final String title;
CounterHome(this.title);
#override
Widget build(BuildContext context) {
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
final userModel = ScopedModel.of<UserModel>(context, rebuildOnChange: true);
final dataModel = ScopedModel.of<DataModel>(context, rebuildOnChange: true);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${userModel.username} pushed the button this many times:'),
Text('${dataModel.data} From data model'),
// Create a ScopedModelDescendant. This widget will get the
// CounterModel from the nearest parent ScopedModel<CounterModel>.
// It will hand that CounterModel to our builder method, and
// rebuild any time the CounterModel changes (i.e. after we
// `notifyListeners` in the Model).
Text('$counter', style: Theme.of(context).textTheme.display1),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Username'),
onPressed: () {
userModel.username = 'Suzanne';
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Data'),
onPressed: () {
dataModel.data = 'data changed';
},
),
)
],
),
),
// Use the ScopedModelDescendant again in order to use the increment
// method from the CounterModel
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
);
}
}
I have created a stateless widget that has a Flutter switch widget I implement this widget in the parent and pass in the required parameters but it won't change value when I press the switch.
I thought it might have been due to the fact that the child widget wasn't stateful but that made no difference.
Here is a brief example of code from my two widget files
class SettingsButton extends StatelessWidget {
final String text;
final bool initalValue;
final void Function(bool) onOffCallback;
SettingsButton({
this.text,
this.initalValue = false,
this.onOffCallback,
});
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
child: SubHeading(text),
),
Switch(
onChanged: isOnOff ? onOffCallback : null,
activeColor: Theme.of(context).accentColor,
value: initalValue,
)
]);
class _SettingsState extends State<Settings> {
bool test = true;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: ListView(
children: [
Column(
children: [
SettingsButton(
text: "Test",
onOffCallback: (test) => setState(() {
print("Called");
test = !test;
}),
initalValue: test,
),
],
)
],
),
);
}
You're setting the test variable recieved in the callback, rather than that defined in the _SettingsState class. What you should have is this for the callback:
onOffCallback: (newTest) => setState(() {
print("$newTest");
test = newTest;
// or (it shouldn't matter)
test = !test;
print("$test");
}),
I'm new to Flutter. I'm trying to send multiple data to another screen:
// screen1.dart
..
Expanded(
child: RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => new Screen2(name: thing.name, email: thing.email, address: thing.address, etc..),
),
);
},
),
),
..
// screen2.dart
class Screen2 extends StatefulWidget{
Screen2({this.name}, {this.email}, {this.address}, etc..);
final String name;
final String email;
final String address;
// etc
#override
State<StatefulWidget> createState() { return new Screen2State();}
}
class Screen2State extends State<Screen2> {
Widget build(BuildContext context) {
return new WillPopScope(
..
child: Scaffold(
..
new Row(
children: <Widget>[
new Text(widget.name),
new Text(widget.email),
new Text(widget.address),
etc..
],
),
)
)
}
But I get the error: A non-null String must be provided to a Text widget.
The data is transferred from TextEditingControllers. It works when there is only 1 data transferred, but fails when there are 2 or more.
What's the correct way to send multiple data between screens?
Everything looks fine but you need to change in the Screen 2 class constructor to this
Screen2({this.name, this.email, this.address, etc..});
Modified Code
// screen1.dart
..
Expanded(
child: RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => new Screen2(name: thing.name, email: thing.email, address: thing.address, etc..),
),
);
},
),
),
..
// screen2.dart
class Screen2 extends StatefulWidget{
Screen2({this.name, this.email, this.address, etc..});
final String name;
final String email;
final String address;
// etc
#override
State<StatefulWidget> createState() { return new Screen2State();}
}
class Screen2State extends State<Screen2> {
Widget build(BuildContext context) {
return new WillPopScope(
..
child: Scaffold(
..
new Row(
children: <Widget>[
new Text(widget.name),
new Text(widget.email),
new Text(widget.address),
etc..
],
),
)
)
}
Note: Text Widget will not accept null values so please make sure you are passing all the values. Or you can initialize the variables with the default value to blank
final String name="";
final String email="";
final String address="";
Consider passing the arguments through route arguments. Refer official doc here https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments