I got a class Restrictions and 3 childs.
I want to pass data from CountryDropDown (and from FieldDropDown class which looks almost same, and from DateSelector class but thats maybe later) class to Restrictions and then to UserOffers (with updating List by tapping a button).
For now I can only define variable in Restrictions class and pass it like this. All on Strings.
Restrictions class:
String selectedCountry = "Argentina";
...
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20),
child: FieldDropDown(),
),
Container(
padding: EdgeInsets.only(top: 10),
margin: EdgeInsets.only(left: 20),
child: CountryDropDown(
),
...
Container(
child: UserOffers(
country: selectedCountry,
field: selectedField,
),
),
UserOffers class:
class UserOffers extends StatefulWidget {
final String country;
final String field;
UserOffers({
this.country,
this.field
}); ...
CountryDropDown class:
static String value;
static String selected;
#override
void initState() {
selected = _CountryDropDownState.selected;
super.initState();
} ...
... ]
.map((label) => DropdownMenuItem(
child: Text(label),
value: label,
))
.toList(),
onChanged: (value) {
setState(() => selected = value);
print(selected);
},
),
),
)
Thank you in advance.
It seems that your problem is related to the state management in Flutter. There are a few approaches that solve this issue, for example:
inherited widget
provider
bloc
redux
If you want to learn more check the official doumentation.
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
I am building a simple calculator and using Riverpod for state management. Though I can update state, the UI is not being updated with the changes... Can someone tell me what I'm doing wrong ?? Here's the code:
Calculator Model
class CalculatorModel {
final bool shouldAppend;
final String equation;
final String result;
const CalculatorModel(
{this.shouldAppend = true, this.equation = '0', this.result = '0'});
CalculatorModel copyWith({
bool? shouldAppend,
String? equation,
String? result,
}) =>
CalculatorModel(
shouldAppend: shouldAppend ?? this.shouldAppend,
equation: equation ?? this.equation,
result: result ?? this.result);
}
Calculator State Notifier Implementation
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:components/calculator_components/calculator_model.dart';
import 'package:math_expressions/math_expressions.dart';
final calculatorProvider =
StateNotifierProvider<CalculatorStateNotifier, List<CalculatorModel>>(
(ref) => CalculatorStateNotifier());
class CalculatorStateNotifier extends StateNotifier<List<CalculatorModel>> {
CalculatorStateNotifier() : super([const CalculatorModel()]);
void append(String calcInput) {
final equation = () {
return state[0].equation == '0'
? calcInput
: state[0].equation + calcInput;
}();
state[0] = CalculatorModel(equation: equation);
}
}
Click function for calculator buttons. State is getting updated, successfully...
void onClickedButton(String calcInput, WidgetRef ref) {
ref.read(calculatorProvider.notifier).append(calcInput);
ref.watch(calculatorProvider);
print(ref.watch(calculatorProvider)[0].equation);
}
Riverpod not updating UI when called in the presentation layer...
#override
Widget build(BuildContext context, WidgetRef ref) {
Size size = MediaQuery.of(context).size;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
margin: const EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: Column(children: [
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.only(top: 15.0, right: 22),
child: Consumer(builder: (context, ref, _) {
return buildCalculatorScreen(
ref.watch(calculatorProvider)[0].equation,
ref.watch(calculatorProvider)[0].result);
}),
)),
),
]),
);
}
}
First, you should not use ref.watch on asynchronous calls, including button calls.
Second, Since our state is immutable, we are not allowed to do state[0] = . You need to update your List in some other way, such as using the spread operator or List.of()
More information here:
StateNotifierProvider from Riverpod
state should be immutable, you have to set a new object/array as the new state.
You can do something like this:
final newState = List.from(state);
newState[0] = CalculatorModel(equation: equation);
state = newState;
I am very new to flutter. I am trying to create List view with Dynamically generated DropDownButton & And Label .No matter what I do this error occurs and dropdown items not updating.
Expected a value of type 'List<DropdownMenuItem<String>>', but got one of type 'List<dynamic>'
This is my listview builder code
ListView.builder(
itemCount: tasksLength,
itemBuilder: (context, index) {
String roleId = taskRoles[index]['roleId'];
List<DropdownMenuItem<String>> _userList = [DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
if (usersList['roles'] != null && usersList['roles'][roleId] != null) {
_userList = usersList['roles'][roleId]['users'].map((item) {
return DropdownMenuItem<String>(value: item['id'],child: Text(item['name'].toString()));
}).toList();
}
return UserSelect(userList: _userList);
},
),
This is my widget class with the DropDownbutton
class UserSelect extends StatefulWidget {
List<DropdownMenuItem<String>>? userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
UserSelect({this.userList});
#override
_UserSelectState createState() => _UserSelectState();
}
class _UserSelectState extends State<UserSelect> {
String _selected_user = '';
String _roleName = 'User Role';
List<DropdownMenuItem<String>> _userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
#override
void initState() {
super.initState();
}
#override
void didUpdateWidget(UserSelect oldWidget) {
if (oldWidget.userList != widget.userList) {
_userList = widget.userList!;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
_roleName,
style: TextStyle(
fontFamily: 'Bilo',
fontSize: 16,
color: const Color(0xff3b3e51),
letterSpacing: 0.224,
height: 1.5,
),
textHeightBehavior:
TextHeightBehavior(applyHeightToFirstAscent: false),
textAlign: TextAlign.left,
),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.0),
color: const Color(0xfff6f6f6),
),
child: DropdownButton<String>(
isExpanded: true,
value: (_selected_user.length > 0) ? _selected_user : null,
icon: const Icon(Icons.keyboard_arrow_down),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.black45),
hint: new Text("Select User"),
underline: Container(
height: 2,
color: Colors.white24,
),
onChanged: (String? newValue) {
setState(() {
_selected_user = newValue!;
});
},
items: _userList),
)
],
);
}
}
Some of codes unnecessary I tried by best to skip this error that is why some junk codes are there.
Please help me to fix this issue or show me right direction.
I think the issue here is the .toList(); method calls here:
List<DropdownMenuItem<String>> _userList =
[DropdownMenuItem<String>(value: '', child: Text('Loading..'))].toList();
toList() in dart returns a list with the type that's supplied to its type parameter. Documentation
When not supplied with a type parameter, I would assume that the toList() method's return type is dynamic.
You can just remove the toList() method call altogether, as you already placed the DropDownMenuItem into a list by placing it between the [square brackets]! Ironically, the type would've been inferred by the list declaration before you overwrote it with toList() and made it dynamic :')
If you DO need to do it this way, you can simply add [items ...].toList<DropdownMenuItem>() which will correctly return a list of type DropdownMenuItem :)
I am struggling with one simple case and it would be lovely if someone could help here.
Let's say that I have some stateful widget. In its state I define a variable (might be of any type) and this variable is later changed through setState() method, where I dynamically assign its value based on some certain criteria. Evertyhing until this point is conducted within one class.
What if I would like to access the value of this variable from another class (totally different page) and if its value changes, rebuild it? Could you please also give me some examples?
Thanks in advance!
That's exactly why State MANAGEMENT exists.
To manage your state through your app.
There are many different options to follow
See:
https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
You can use provider package in that case.
In yaml file add,
provider: ^4.3.2+4
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
Counter _counterProvider;
#override
void initState() {
super.initState();
_counterProvider = Provider.of(context, listen: false);
}
void updateCounter() {
_counterProvider.setCount();
}
#override
Widget build(BuildContext context) {
Counter _counterProvider = Provider.of(context);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
_counterProvider.count.toString(),
style: TextStyle(
fontSize: 22,
),
),
),
RaisedButton(
onPressed: updateCounter,
child: Text('Click'),
),
],
),
),
);
}
}
// class for storing data(Counter.dart)
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier { // create a common file for data
int _count = 0;
int get count => _count;
void setCount() {
_count++;
notifyListeners();
}
}
Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore