Flutter DropdownButton NoSuchMethodError: The getter value was called on null - flutter

I'm trying to populate a dropdown button in my flutter app with data coming from my restful api. However i get the error above.
Here is my model;
class SavingsCategory extends Equatable{
final int id;
final String name;
SavingsCategory({
#required this.id,
#required this.name
});
#override
List<Object> get props => [name, id];
}
My repository fetching the data
#override
Future<List<SavingsCategory>> getSavingsCategory()
async {
var token = await tokenRepository.getToken();
final response = await http.get(
'$baseUrl/user/savings-category',
headers: {HttpHeaders.authorizationHeader: 'Bearer $token'},
);
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<SavingsCategory> categoryList = data['savingsCategory'].map<SavingsCategory>((json) {
return SavingsCategory.fromJson(json);
}).toList();
return categoryList;
} else {
throw new Exception("Couldn't get any saving categories");
}
}
My bloc code
class SavingsCategoryBloc {
final repository = SavingsRepository();
final _savingsCategories = PublishSubject<List<SavingsCategory>>();
Stream<List<SavingsCategory>> get savingsCategories => _savingsCategories.stream;
fetchSavingsCategories() async {
final categories = await repository.getSavingsCategory();
_savingsCategories.sink.add(categories);
}
dispose(){
_savingsCategories.close();
}
}
Finally my widget
class _StartSavingPageState extends State<StartSavingPage> {
final SavingsCategoryBloc bloc = SavingsCategoryBloc();
#override
void initState() {
bloc.fetchSavingsCategories();
super.initState();
}
#override
Widget build(BuildContext context) {
....
Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0, vertical: 10.0),
child: StreamBuilder<List<SavingsCategory>>(
stream: bloc.savingsCategories,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('No Savings Category'),
value: '',
),
],
onChanged: (String value) {
setState(() {
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}
return DropdownButton(
value: category,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
category = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}),
),
}
}
How can i fix this error? I know the data fetching works just fine. I'm definitely missing something in my widget. Any help would be appreciated.

The DropdownButton value must in item values or must be null.
DropdownButton(
value: categoryId,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
categoryId = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);

The mistake you've made is not returning the DropdownMenuItem from the map.
So:
snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
})
should instead be:
snapshot.data.map((category) =>
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
)

Related

Putting a list of objects in ItemBuilder, to generate Checkboxes and make Checkboxes clickable. (Dart/Flutter)

The problem is, that I don't know how I can make the checkboxes(onchange with the reserved value) get ticked when i click on one. Now I can't click them at all. I want to iterate over the List of Objects in the Itembuilder and use their reserved attribute to have a boolean value for the checkboxes. Also I want that when I click the Checkbox, the Capacity Counter of this object gets clicked, should get plus one. How can I achieve this? Thankful for all tips.
class Parking extends StatefulWidget {
const Parking({Key? key, this.bookingdays}) : super(key: key);
#override
State<Parking> createState() => _ParkingState();
final List<Bookingday>? bookingdays;
class _ParkingState extends State<Parking> {
void initState() {
super.initState();
final buchungsTage = widget.bookingdays ??
List<Bookingday>.filled(
tage.getWeek(_date).length,
Bookingday(
day: _date,
reserved: false,
capacityCounter: 0,
maxCapacity: 4));
}
// and then later in the Build Method:
ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: tage.getWeek(_date).length,
itemBuilder: (context, index) {
final bookingDays = widget.bookingdays ??
List<Bookingday>.filled(
tage.getWeek(_date).length,
Bookingday(
day: _date,
reserved: false,
capacityCounter: 0,
maxCapacity: 4));
final day = tage.getWeek(_date);
return Card(
child: CheckboxListTile(
secondary: Text(
Tage.formatDate(day[index]),
style: const TextStyle(
fontSize: 20,
),
),
title: Center(
child: Text(
'${bookingDays[index].capacityCounter}/$_maxParkPlaces',
style: TextStyle(
color: _increment != _maxParkPlaces
? Colors.green
: Colors.red,
fontSize: 20,
),
),
),
value: bookingDays[index].reserved,
onChanged: (value) => setState(
() {
bookingDays[index].reserved = value!;
bookingDays[index].capacityCounter = 2;
},
),
),
);
},
),
),
}
You can follow this model class and widget
class Parking extends StatefulWidget {
#override
State<Parking> createState() => _ParkingState();
}
class Item {
final String data;
final bool isChecked;
Item({
required this.data,
required this.isChecked,
});
Item copyWith({
String? data,
bool? isChecked,
}) {
return Item(
data: data ?? this.data,
isChecked: isChecked ?? this.isChecked,
);
}
}
class _ParkingState extends State<Parking> {
late List<Item> items =
List.generate(12, (index) => Item(data: "data $index", isChecked: false));
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
child: CheckboxListTile(
title: Center(
child: Text(
' s $index',
style: TextStyle(
fontSize: 20,
),
),
),
value: items[index].isChecked,
onChanged: (value) => setState(
() {
items[index] =
items[index].copyWith(isChecked: !items[index].isChecked);
},
),
),
);
},
),
);
}
}
try to Put the code below outside of build widget
final bookingDays = widget.bookingdays ??
List<Bookingday>.filled(
tage.getWeek(_date).length,
Bookingday(
day: _date,
reserved: false,
capacityCounter: 0,
maxCapacity: 4));

Custom Widget not Rendering

I have made a custom DropDown Picker the problem is when I switch it, the widget does not get rendered
There are 2 Dropdowns on the UI. In different cases, the child dropDown may or may not be visible.
The problem only occurs if I have both parent and child dropdowns and in the next case, the two dropdowns are both visible.
These are the below cases of how my Dynamic UI is render
case 1 ) DropDown1 and Drop DropDown2 on the UI (Drop Down 2 is parent widget)
when the user clicks on dropDown 2 items the Main UI gets rendered.
(Drop Down 2 items Minutes, Hours, Day, Week)
DropDown 1 item changes as per drop down 2 )
class CustomDropDown extends StatefulWidget {
final List<String> dropDownList;
final defaultVal;
final Function selectedItem;
final customDropDownId;
final isExpanded;
final dropDownType;
const CustomDropDown(
{Key? key,
required this.dropDownList,
required this.defaultVal,
required this.selectedItem,
this.customDropDownId,
this.isExpanded = false,
required this.dropDownType})
: super(key: key);
#override
_CustomDropDownState createState() => _CustomDropDownState();
}
class _CustomDropDownState extends State<CustomDropDown> {
var chosenValue;
#override
void initState() {
super.initState();
print("initState");
chosenValue = widget.defaultVal;
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
#override
void dispose() {
super.dispose();
print("dispose");
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
print("dropDownList ${widget.dropDownList} defaultVal ${widget.defaultVal} chosenValue ${chosenValue} ");
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
}
Parent Widget
Widget repeatEveryWidget(chosenValue) {
if (chosenValue == dropDownJobList[0] ||
chosenValue == dropDownJobList[1]) {
bool isMinutesWidget = chosenValue == dropDownJobList[0];
List<String> dropDownList = isMinutesWidget ? minutesList : hourList;
return CustomDropDown(
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
} else {
return const SizedBox();
}
}
Parent Calling
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(REPEAT_EVERY),
SizedBox(
width: 10.w,
),
repeatEveryWidget(chosenValue),
SizedBox(
width: 10.w,
),
CustomDropDown(
dropDownList: dropDownCustomList,
defaultVal: chosenValue,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem:
(String selectedVal, DropDownsType dropDownId) {
setState(() {
chosenValue = selectedVal;
});
},
customDropDownId:
DropDownsTypeRepeatPicker,
),
],
),
)
Output
If the user selects item 1 Minute and then selects any item other than hours the child drop down gets removed from UI. But when the user selects hours after a minute the Items in Child widget renders but the defaultValue of this does not pick a new value it retains the old data that was picked in minutes as the UI has not been destroyed.
The answer to the above question lies in statement management in a flutter.
As there are two lists which has string object and some data are identical like "5","10"
const List<String> minutesListConfig = ['5', '10', '15', '30', '45', '60'];
const List<String> hourListConfig = ['1', '2', '3','4', '5', '6', '7','8', '9', '10', '11','12'];
As said by the flutter team every widget has an element that keeps track of its state whenever you try to render identical object type in dropdown list it will not render the new list.
If you want the widget tree to make sure to render the dropdown widget in 2 different ways then you will have to use KEY
In this case, the Object key can be used to make sure in background flutter makes 2 different dropdown lists for both the cases and does not render the dropdown as 1 widget.
return CustomDropDown(
key: ObjectKey(chosenValue),
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
key:widget.key
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
Flutter team video on keys
https://www.youtube.com/watch?v=kn0EOS-ZiIc

Flutter: How to assign other value (object) as Text Editing Controller in Another Field?

I'm building a form that has contact name and phone number fields. User will be able to choose (tap) contact from previously saved contact list, and this should display name and phone numbers at their respective fields.
To achieve that I'm using TypeAheadFormField from Flutter_Form_Builder Package version: 3.14.0 to build my form.
I successfully assign _nameController from local database in the default TypeAheadFormField controller.
But I can't assign _mobileController from the same choice I tapped to FormBuilderTextField.
I managed to get "name'-value with TypeAheadFormField, but everytime I switch the choices from suggestions,
the _mobileController.text didn't update on FormBuilderTextField
My code as follow:
import 'package:myApp/customer.dart';
import 'package:myApp/db_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
DatabaseHelper _dbHelper;
Customer _customer = Customer();
List<Customer> _customerList = [];
final _formKey = GlobalKey<FormBuilderState>();
final _cfKey = GlobalKey<FormBuilderState>();
final _nameController = TextEditingController();
final _inputContactNameController = TextEditingController();
final _inputContactPhoneController = TextEditingController();
var _mobileController = TextEditingController();
#override
void initState() {
super.initState();
_refreshBikeSellerList();
setState(() {
_dbHelper = DatabaseHelper.instance;
});
_mobileController = TextEditingController();
_mobileController.addListener(() {
setState(() {});
});
}
#override
void dispose() {
_mobileController.dispose();
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTypeAhead(
attribute: 'contact_person',
initialValue: _customer,
controller: _nameController,
onChanged: (val) {},
itemBuilder: (context, Customer _customer) {
return ListTile(
title: Text(_customer.name),
subtitle: Text(_customer.mobile),
);
},
selectionToTextTransformer: (Customer c) => c.name,
suggestionsCallback: (query) {
if (query.isNotEmpty) {
var lowercaseQuery = query.toLowerCase();
return _customerList.where((_customer) {
return _customer.name
.toLowerCase()
.contains(lowercaseQuery);
}).toList(growable: false)
..sort((a, b) => a.name
.toLowerCase()
.indexOf(lowercaseQuery)
.compareTo(
b.name.toLowerCase().indexOf(lowercaseQuery)));
} else {
return _customerList;
}
},
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontSize: 17,
letterSpacing: 1.2,
color: Colors.black,
fontWeight: FontWeight.w300,
),
// controller: guessMotor1,
),
onSuggestionSelected: (val) {
if (val != null) {
return _customerList.map((_customer) {
setState(() {
_mobileController.text = _customer.mobile;
});
}).toList();
} else {
return _customerList;
}
},
),
FormBuilderTextField(
controller: _mobileController,
attribute: 'mobile',
readOnly: true,
style: TextStyle(fontSize: 17),
decoration: InputDecoration(
hintText: 'mobile',
),
),
SizedBox(height: 40),
Container(
child: RaisedButton(
onPressed: () async {
await manageContact(context);
},
child: Text('Manage Contact'),
),
),
],
),
),
);
}
manageContact(BuildContext context) async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Manage Contact',
textAlign: TextAlign.center,
),
titleTextStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 17,
color: Colors.black45,
letterSpacing: 0.8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
content: FormBuilder(
key: _cfKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// SizedBox(height: 10),
InkResponse(
onTap: () {},
child: CircleAvatar(
radius: 30,
child: Icon(
Icons.person_add,
color: Colors.grey[100],
),
backgroundColor: Colors.grey[500],
),
),
SizedBox(height: 10),
Container(
width: MediaQuery.of(context).size.width * 0.5,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
maxLength: 20,
controller: _inputContactNameController,
textAlign: TextAlign.start,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
attribute: 'contact_person',
decoration: InputDecoration(
prefixIcon: Icon(
Icons.person_outline,
size: 22,
)),
onChanged: (val) {
setState(() {
_customer.name = val;
_formKey
.currentState.fields['contact_person'].currentState
.validate();
});
},
autovalidateMode: AutovalidateMode.always,
validators: [
FormBuilderValidators.required(),
FormBuilderValidators.maxLength(20),
FormBuilderValidators.minLength(2),
],
),
),
Container(
width: MediaQuery.of(context).size.width * 0.5,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
attribute: 'phone_number',
controller: _inputContactPhoneController,
textAlign: TextAlign.start,
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.phone_android,
size: 22,
)),
onChanged: (val) {
setState(() {
_customer.mobile = val;
_formKey.currentState.fields['phone_number'].currentState
.validate();
});
},
validators: [
FormBuilderValidators.required(),
FormBuilderValidators.numeric(),
],
valueTransformer: (text) {
return text == null ? null : num.tryParse(text);
},
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RaisedButton(
color: Colors.white,
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
RaisedButton(
color: Colors.grey[400],
child: Text(
'Save',
style: TextStyle(color: Colors.white),
),
onPressed: () async {
try {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
if (_customer.id == null)
await _dbHelper.insertBikeContact(_customer);
else
await _dbHelper.updateCustomer(_customer);
_refreshBikeSellerList();
_formKey.currentState.reset();
_inputContactNameController.clear();
_inputContactPhoneController.clear();
Navigator.of(context).pop();
}
} catch (e) {
print(e);
}
},
)
],
),
],
),
),
),
);
}
_refreshBikeSellerList() async {
List<Customer> x = await _dbHelper.getCustomer();
setState(() {
_customerList = x;
});
}
}
Is there any possible way to update _mobileController as I tap?
Any help would be much appreciated.
Thank you in advance.
EDITED:
class where I save the customer data:
class Customer {
int id;
String name;
String mobile;
static const tblCustomer = 'Customer';
static const colId = 'id';
static const colName = 'name';
static const colMobile = 'mobile';
Customer({
this.id,
this.name,
this.mobile,
});
Map<String, dynamic> toMap() {
var map = <String, dynamic>{colName: name, colMobile: mobile};
if (id != null) map[colId] = id;
return map;
}
Customer.fromMap(Map<String, dynamic> map) {
id = map[colId];
name = map[colName];
mobile = map[colMobile];
}
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Customer &&
runtimeType == other.runtimeType &&
name == other.name;
#override
int get hashCode => name.hashCode;
#override
String toString() {
return name;
}
}
Here is my database:
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'customer.dart';
class DatabaseHelper {
static const _databaseVersion = 1;
static const _databaseName = 'Kiloin.db';
DatabaseHelper._();
static final DatabaseHelper instance = DatabaseHelper._();
Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
Directory dataDirectory = await getApplicationDocumentsDirectory();
String dbPath = join(dataDirectory.path, _databaseName);
return await openDatabase(
dbPath,
version: _databaseVersion,
onCreate: _onCreateDB,
);
}
_onCreateDB(Database db, int version) async {
await db.execute('''
CREATE TABLE ${Customer.tblCustomer}(
${Customer.colId} INTEGER PRIMARY KEY AUTOINCREMENT,
${Customer.colName} TEXT NOT NULL,
${Customer.colMobile} TEXT NOT NULL
)
''');
}
Future<int> insertBikeContact(Customer customer) async {
Database db = await database;
return await db.insert(Customer.tblCustomer, customer.toMap());
}
Future<List<Customer>> getCustomer() async {
Database db = await database;
List<Map> contact = await db.query(Customer.tblCustomer);
return contact.length == 0
? []
: contact.map((e) => Customer.fromMap(e)).toList();
}
Future<int> updateCustomer(Customer customer) async {
Database db = await database;
return await db.update(Customer.tblCustomer, customer.toMap(),
where: '${Customer.colId}=?', whereArgs: [customer.id]);
}
Future<int> deleteContact(int id) async {
Database db = await database;
return await db.delete(Customer.tblCustomer,
where: '${Customer.colId}=?', whereArgs: [id]);
}
}
The value that you get from onSuggestionSelected is the customer. Use that value to update _mobileController.text.
onSuggestionSelected: (customer) {
if (customer != null) {
setState(() {
_mobileController.text = customer.mobile;
});
}
}

Is it possible to POST Data using localhost woocommerce rest api in flutter

Is it possible to POST data from flutter app to woocommerce localhost using woocommerce localhost server rest api.
i have GET & POST data with private domain but i want to POST & GET data with localhost woocommerce rest api. i have setup my wordpress and woocommerce on localhost I am trying to make flutter ecommerce app and trying to GET & POST data from woocommerce localhost. but its not working and i dont want to send from private domain rest api, i can get data on postman if i select OAuth 1.0 but if i dont use OAuth 1.0 i cant get data.
Config.dart
class Config {
static String key =
'ck_00000000000000000000000000';
static String sceret =
'cs_00000000000000000000000000';
static String url = 'http://10.0.2.2:80/wordpress_new/wp-json/wc/v3/';
static String customerURL = 'customers';
}
customer.dart
class CustomerModel {
String email;
String firstName;
String lastName;
String password;
CustomerModel({
this.email,
this.firstName,
this.lastName,
this.password,
});
Map<String, dynamic> toJson() {
Map<String, dynamic> map = {};
map.addAll({
'email': email,
'first_name': firstName,
'last_name': lastName,
'password': password,
'username': email,
});
return map;
}
}
apiservice.dart
class APIService {
Future<bool> createCustomer(CustomerModel model) async {
var authToken = base64.encode(
utf8.encode(Config.key + ':' + Config.sceret),
);
bool ret = false;
try {
var response = await Dio().post(
Config.url +
Config.customerURL,
data: model.toJson(),
options: new Options(headers: {
HttpHeaders.authorizationHeader: 'Basic $authToken',
HttpHeaders.contentTypeHeader: 'application/json',
}));
if (response.statusCode == 201) {
ret = true;
}
} on DioError catch (e) {
if (e.response.statusCode == 404) {
print(e.response.statusCode);
ret = false;
} else {
print(e.message);
print(e.request);
ret = false;
}
}
return ret;
}
Future<LoginResponseModel> loginCustomer(
String username,
String password,
) async {
LoginResponseModel model;
try {
var response = await Dio().post(Config.tokenURL,
data: {
'username': username,
'password': password,
},
options: new Options(headers: {
HttpHeaders.contentTypeHeader: 'application/x-www-form-urlencoded',
}));
if (response.statusCode == 200) {
model = LoginResponseModel.fromJson(response.data);
}
} on DioError catch (e) {
print(e.message);
}
return model;
}
}
signuppage.dart
class SignupPage extends StatefulWidget {
#override
_SignupPageState createState() => _SignupPageState();
}
class _SignupPageState extends State<SignupPage> {
APIService apiService;
CustomerModel model;
GlobalKey<FormState> globalKey = GlobalKey<FormState>();
bool hidePassword = true;
bool isApiCallProcess = false;
#override
void initState() {
apiService = new APIService();
model = new CustomerModel();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
automaticallyImplyLeading: true,
title: Text('Sign Up'),
),
body: ProgressHUD(
child: Form(
key: globalKey,
child: _formUI(),
),
inAsyncCall: isApiCallProcess,
opacity: 0.3),
);
}
Widget _formUI() {
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10.00),
child: Container(
child: Align(
alignment: Alignment.topLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
FormHelper.fieldLabel('First Name'),
FormHelper.textInput(
context,
model.firstName,
(value) => {
this.model.firstName = value,
},
onValidate: (value) {
if (value.toString().isEmpty) {
return 'Please enter First Name.';
}
return null;
},
),
FormHelper.fieldLabel('Last Name'),
FormHelper.textInput(
context,
model.lastName,
(value) => {
this.model.lastName = value,
},
onValidate: (value) {
return null;
},
),
FormHelper.fieldLabel('Email Id'),
FormHelper.textInput(
context,
model.email,
(value) => {
this.model.email = value,
},
onValidate: (value) {
if (value.toString().isEmpty) {
return 'Please enter Email id.';
}
if (value.isNotEmpty && !value.toString().isValidEmail()) {
return 'Please enter valid email id';
}
},
),
FormHelper.fieldLabel('Password'),
FormHelper.textInput(
context,
model.password,
(value) => {
this.model.password = value,
},
onValidate: (value) {
if (value.toString().isEmpty) {
return 'Please enter Password.';
}
return null;
},
obscureText: hidePassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
color: Theme.of(context).accentColor.withOpacity(0.4),
icon: Icon(
hidePassword ? Icons.visibility_off : Icons.visibility,
),
),
),
SizedBox(
height: 20,
),
Center(
child: FormHelper.saveButton(
'Register',
() {
if (validateAndSave()) {
print(model.toJson());
setState(() {
isApiCallProcess = true;
});
apiService.createCustomer(model).then(
(ret) {
setState(() {
isApiCallProcess = false;
});
if (ret) {
FormHelper.showMessage(
context,
'WooCommerce App',
'Registration Successfull',
'Ok',
() {
Navigator.of(context).pop();
},
);
} else {
FormHelper.showMessage(
context,
'WooCommerce App',
'Email Id already registered.',
'Ok',
() {
Navigator.of(context).pop();
},
);
}
},
);
}
},
),
)
],
),
),
),
),
);
}
bool validateAndSave() {
final form = globalKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
}
form_helper.dart
class FormHelper {
static Widget textInput(
BuildContext context,
Object initialValue,
Function onChanged, {
bool isTextArea = false,
bool isNumberInput = false,
obscureText: false,
Function onValidate,
Widget prefixIcon,
Widget suffixIcon,
}) {
return TextFormField(
initialValue: initialValue != null ? initialValue.toString() : "",
decoration: fieldDecoration(
context,
"",
"",
suffixIcon: suffixIcon,
),
obscureText: obscureText,
maxLines: !isTextArea ? 1 : 3,
keyboardType: isNumberInput ? TextInputType.number : TextInputType.text,
onChanged: (String value) {
return onChanged(value);
},
validator: (value) {
return onValidate(value);
},
);
}
static InputDecoration fieldDecoration(
BuildContext context,
String hintText,
String helperText, {
Widget prefixIcon,
Widget suffixIcon,
}) {
return InputDecoration(
contentPadding: EdgeInsets.all(6),
hintText: hintText,
helperText: helperText,
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 1,
),
),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 1,
),
),
);
}
static Widget fieldLabel(String labelName) {
return new Padding(
padding: EdgeInsets.fromLTRB(0, 5, 0, 10),
child: Text(
labelName,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
),
);
}
static Widget saveButton(String buttonText, Function onTap,
{String color, String textColor, bool fullWidth}) {
return Container(
height: 50.0,
width: 150,
child: GestureDetector(
onTap: () {
onTap();
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.redAccent,
style: BorderStyle.solid,
width: 1.0,
),
color: Colors.redAccent,
borderRadius: BorderRadius.circular(30.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text(
buttonText,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 1,
),
),
),
],
),
),
),
);
}
static void showMessage(
BuildContext context,
String title,
String message,
String buttonText,
Function onPressed,
) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(title),
content: new Text(message),
actions: [
new FlatButton(
onPressed: () {
return onPressed();
},
child: new Text(buttonText),
)
],
);
},
);
}
}
progressHUD.dart
class ProgressHUD extends StatelessWidget {
final Widget child;
final bool inAsyncCall;
final double opacity;
final Color color;
final Animation<Color> valueColor;
ProgressHUD({
Key key,
#required this.child,
#required this.inAsyncCall,
this.opacity = 0.3,
this.color = Colors.grey,
this.valueColor,
}) : super(key: key);
#override
Widget build(BuildContext context) {
List<Widget> widgetList = new List<Widget>();
widgetList.add(child);
if (inAsyncCall) {
final modal = new Stack(
children: [
new Opacity(
opacity: opacity,
child: ModalBarrier(dismissible: false, color: color),
),
new Center(child: new CircularProgressIndicator()),
],
);
widgetList.add(modal);
}
return Stack(
children: widgetList,
);
}
}

StatefulWidget - FLutter

I need to edit this code, in a way to define only one variable widget which can be able to change on every state to a different widget type.
I need to be able to make a dynamic form no matter what the question and its type is, the way i handle it is somehow complex and not efficient.
so is there any idea on how to change the same variable for different widget on every setState()
`Column(
children: <Widget>[
questionText,
textCounter > 0 ? textField : SizedBox(),
selectCounter > 0 ? selectField : SizedBox()
],
)),`FutureBuilder(
future: fetchQuestions(),
builder: (context, snapshot) {
if (snapshot.hasData) {
for (var i = 0; i < snapshot.data.length; i++) {
var temp = snapshot.data[i]['question_value'].toString();
var type = snapshot.data[i]['question_type'].toString();
questionsList.add(temp);
typeList.add(type);
}
return Align(
alignment: Alignment.bottomRight,
child: RaisedButton(
onPressed: () {
changeQuest(questionsList, typeList,
snapshot.data.length, snapshot.data);
},
child: Text('next'),
),
);
} else
return Center(child: CircularProgressIndicator());
},
),
changeQuest(List questions, List type, length, data) {
setState(() {
textCounter = 0;
selectCounter = 0;
integerCounter = 0;
if (counter < length) {
questionText = Text(questions[counter]);
if (type[counter] == 'Integer') {
textCounter++;
textField = TextFormField(
decoration: new InputDecoration(labelText: "Enter your number"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
], // Only numbers can be entered
);
} else if (type[counter] == 'Text') {
textCounter++;
textField = TextFormField(
decoration: new InputDecoration(labelText: "Enter a text"),
keyboardType: TextInputType.text,
);
} else if (type[counter] == 'Select') {
selectCounter++;
for (var i = 0; i < data[counter]['answers'].length; i++) {
answersList
.add(data[counter]['answers'][i]['answer_value'].toString());
}
dropDownValue = answersList[0];
selectField = DropdownButton<String>(
value: dropDownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (value) {
setState(() {
dropDownValue = value;
});
},
items: answersList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
print (dropDownValue);
}
}
counter++;
});
}
as #proversion said in the comments, you can check in the widget tree, if a condition returns true or false.
Before you enter the child you could check with an inline if-statement like so:
questionType == 'dropdown' ? (Widget for True) : (Widget for False)
Or if you have to do a complex check, I would do this in the build Method before the return of the widget and set a boolean value there, which represents your check result.
Then you can use this value (example: isTrue) in the widget tree like isTure ? (Widget for True) : (Widget for False).
Here is a sample code, that should work.
import 'package:flutter/material.dart';
class WidgetWithDifferentChildren extends StatefulWidget {
#override
_WidgetWithDifferentChildrenState createState() =>
_WidgetWithDifferentChildrenState();
}
class _WidgetWithDifferentChildrenState
extends State<WidgetWithDifferentChildren> {
String questionType = '';
String dropdownValue = 'SelectItem';
String textValue = '';
TextEditingController txtCtrl = TextEditingController();
#override
void dispose() {
// TODO: implement dispose when using TextEditingController
txtCtrl.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: questionType == ''
? Text('no Question Type')
: questionType == 'dropdown'
? DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
// Do something with the new Value
print('New DropDown value = $newValue');
setState(() {
dropdownValue = newValue;
});
},
items: <String>[
'SelectItem',
'Item 1',
'Item 2',
'Item 3',
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
)
: questionType == 'textfield'
? TextFormField(
controller: txtCtrl,
onChanged: (value) {
// Do something with the new Value
print('New TextField value = $value');
setState(() {
textValue = value;
});
},
)
: Text('Question Type does not match'),
);
}
}
UPDATE
acc. to your provided code, you may want to check the following. I created a separate class which will return the right widget for the question. Just pass the type and additional the dropDownList to the function.
General I would suggest to store the questions and the corresponding answers in the same array, this would be a easy call of the function like getInputWidget(type:question[i].type, dropDownList:question[i].dropDownList).
Source Code for above example
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class WidgetWithDifferentChildren extends StatefulWidget {
#override
_WidgetWithDifferentChildrenState createState() =>
_WidgetWithDifferentChildrenState();
}
class _WidgetWithDifferentChildrenState
extends State<WidgetWithDifferentChildren> {
String questionType = '';
String inputValue = '';
List<String> answers = [];
int questionID = 0;
TextEditingController txtCtrl = TextEditingController();
List<Map<String, String>> questionList = [
{'question_value': 'text question ', 'question_type': 'text'},
{'question_value': 'number question ', 'question_type': 'number'},
{'question_value': 'select question ', 'question_type': 'select'},
{'question_value': 'last question ', 'question_type': 'text'},
];
List<String> dropDownList = [
'Select an Item',
'Answer A',
'Answer B',
'Answer C',
];
#override
void dispose() {
// TODO: implement dispose when using TextEditingController
txtCtrl.dispose();
super.dispose();
}
Widget getInputWidget({#required String type, List<String> dropDownList}) {
Widget inputW;
if (type == 'number' || type == 'text') {
inputW = TextFormField(
controller: txtCtrl,
decoration: new InputDecoration(labelText: "Enter a $type"),
keyboardType:
type == 'text' ? TextInputType.text : TextInputType.number,
inputFormatters: <TextInputFormatter>[
type == 'text'
? LengthLimitingTextInputFormatter(50)
: WhitelistingTextInputFormatter.digitsOnly
], // Only numbers can be entered
onChanged: (value) {
setState(() {
inputValue = value;
});
},
);
} else if (type == 'select') {
if (inputValue.length == 0) {
// set the input Value for the first time
inputValue = dropDownList[0];
}
inputW = DropdownButton<String>(
value: inputValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (value) {
setState(() {
inputValue = value;
});
},
items: dropDownList.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
return inputW;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 30),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RaisedButton(
onPressed: () {
setState(() {
answers.add(inputValue);
inputValue = '';
txtCtrl.clear();
questionID = questionID + 1;
});
// unfocus to close the Keyboard
// conrtibution to: https://flutterigniter.com/dismiss-keyboard-form-lose-focus/
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: Text('next'),
),
getInputWidget(
type: questionList[questionID]['question_type'],
dropDownList: dropDownList),
Divider(thickness: 2),
Text('You enter: $inputValue'),
Divider(thickness: 2),
Text('Your answers are:'),
Flexible(
child: ListView.builder(
itemCount: answers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index. ${answers[index]}'),
);
}),
),
],
),
),
);
}
}