How to display list of map into DropdownMenuItem in Flutter - list

I'm new with Flutter.
I want to display DropdownMenuItem from my list variable.
Check out my code.
// my list variable
List listUserType = [
{'name': 'Individual', 'value': 'individual'},
{'name': 'Company', 'value': 'company'}
];
// items property in DropdownMenuItem
return DropdownButtonFormField<List<Map>>(
decoration: InputDecoration(
prefixIcon: Icon(Icons.settings),
hintText: 'Organisation Type',
filled: true,
fillColor: Colors.white,
errorStyle: TextStyle(color: Colors.yellow),
),
items: listUserType.map((map) {
return DropdownMenuItem(
child: Text(map['name']),
value: map['value'],
);
}).toList());
This is the result I got
I/flutter (22528): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (22528): The following assertion was thrown building RegisterPage(dirty, state: RegisterPageState#5b83a):
I/flutter (22528): type 'List<DropdownMenuItem<dynamic>>' is not a subtype of type
I/flutter (22528): 'List<DropdownMenuItem<List<Map<dynamic, dynamic>>>>'
I do not know what is causing the error.

Try removing <List<Map>>
return DropdownButtonFormField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.settings),
hintText: 'Organisation Type',
filled: true,
fillColor: Colors.white,
errorStyle: TextStyle(color: Colors.yellow),
),
items: listUserType.map((map) {
return DropdownMenuItem(
child: Text(map['name']),
value: map['value'],
);
}).toList());

Change DropdownButtonFormField<List<Map>> to DropdownButtonFormField<String> and add a String type parameter to return DropdownMenuItem<String>

I show the complete flow using a DropDownButtonField of YourClassView type. I create a variable called _listMenuItems which will hold your DropdownMenuItem. You must property cast its <DropdownMenuItem> or an casting error will ocurr. The _currentComboView points keeps the YourClassView object that the user has selected from the DropdownButtonField. The _loadStatusCombo is called from initState() override. It called a provder to get a list of YourClassView objects called dataViews. I map the dataViews into the _listMenuItems by casting it and using the map function. Make sure to include .tolist to flat out the results of the mapping.
YourClassView has two field: DisplayValue and DatabaseValue
YourClassView _currentComboView;
List<DropdownMenuItem> _listMenuItems =
<DropdownMenuItem<YourClassView>>[];
DropdownButtonFormField<YourClassView>(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(2.0),
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: "Select a Status",
fillColor: Colors.orange),
items: _listMenuItems,
isDense: true,
isExpanded: true,
value: this._currentComboView,
validator: (value) =>
value == null ? 'field required' : null,
onChanged: (YourClassView value) {
setState(() {
this._currentComboView = value;
});
}),
_loadStatusCombo() {
Provider.of<Api>(context, listen: false)
.getComboViews()
.then((dataViews) {
setState(() {
_listMenuItems =
dataViews.map<DropdownMenuItem<YourClassView>>((item) {
return DropdownMenuItem<YourClassView>(
value: item, child: Text(item.displayValue));
}).toList();
});
});
}

Related

Error "The method 'call' was called on null" for generating a list

In my application I use the following code:
#override
Widget build(BuildContext context) {
List<double> listMax = List.from(data_snap()['history']);
final listMaxreversed = List.from(listMax.reversed);
final list_one_week = listMaxreversed.take(
data_snap()['history']['1w']);
final list_one_week_list = List.from(list_one_week);
final list_one_week_list_rev = List.from(list_one_week_list.reversed);
List<FlSpot> spots_one_week =
list_one_week_list_rev
.asMap()
.entries
.map((e) {
return FlSpot(e.key.toDouble(), e.value);
}).toList();
print(spots_one_week);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.lightGreen),
backgroundColor: Colors.grey[100],
title: const Text('Details', style:
TextStyle(color: Colors.lightGreen)),
),
body: Padding(
//some code here...
Row(
children: [ _loading ? CircularProgressIndicator():
new Container(
child:
LineChart(
LineChartData(
lineTouchData: LineTouchData(enabled: true),
gridData: FlGridData(
show: false,
drawHorizontalLine: false,
drawVerticalLine: false,
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.black,
strokeWidth: 1,
);
},
),
lineBarsData: [
LineChartBarData(
spots: spots_one_week, //inserts the spots here
A brief moment I get the following error on my phone. After this moment the chart is displayed correctly.
The method 'call' was called on null.
Receiver: null
Tried calling: call()
I think I have to include something like an async task. The error occurs directly when I define listMax. How can I solve this?

Insert Input Chips From List<String> into TextFormField Flutter on Button Click

I am having a List<String> of cities below
List<String> cities = ["Nairobi", "Tokyo", "Moscow", "Bogota",
"Helsinki", "Denver", "Stockholm", "Oslo"];
I am taking the cities list and just setting the text to TextFormField using TextEditingController upon a city being selected in showCityPickerDialog which updates the cities string list. Below is the code
TextFormField(
cursorHeight: 20,
enableInteractiveSelection: false,
readOnly: true,
focusNode: FocusNode(),
controller: TextEditingController(
text:
cities.isEmpty ? null : "$cities"),
onTap: () {
showCityPickerDialog();
},
decoration: InputDecoration(
isDense: true,
labelText: "Select Cities",
border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: () {
showCityPickerDialog();
},
icon: const Icon(Icons.location_city)),
),
validator: (phoneNo) {},
keyboardType: TextInputType.multiline,
maxLines: null,
)
What i would like to achieve is instead of just setting the text using TextEditingController(text: cities.isEmpty ? null : "$cities") i would like to use Chips like in below code where the number of chips inside the TextFormField will depend on the length of the list
Chip(
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text('City Initial'),
),
label: Text('City'),
)
Maybe you can add chips inside your TextFormField decoration as prefix Widget like this:
prefix: Row(
children: cities
.map(
(e) => Chip(
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text('City Initial'),
),
label: Text(e),
),
)
.toList(),
)

Flutter 'map' Dynamic call of null. Receiver: Instance of '_Future<dynamic>' Arguments: [Instance of '(dynamic) => Builder']

i am trying to make a banner using this list which can be accessed with function "getBannerList" instead of having to manually write a list in this carousel
if i set items by manually putting a list at items: ['base64string','base64string','base64string'].map it will work fine but when i replace it with the function it causes this error
Error
The following NoSuchMethodError was thrown building MyApp(dirty, dependencies: [MediaQuery], state: _MyAppState#5a46f):
'map'
Dynamic call of null.
Receiver: Instance of '_Future<dynamic>'
Arguments: [Instance of '(dynamic) => Builder']
Code for carousel
Dependencies: carousel_slider: ^4.0.0
CarouselSlider(
options: CarouselOptions(
height: MediaQuery.of(context).size.height * 0.15,
autoPlay: true,
autoPlayInterval: Duration(seconds: 5),
initialPage: 0,
),
items: getBannerList().map((e) { // <---- if i replaced this with ['asdf','asdf,'asdf].map((e) it works fine
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(
image: DecorationImage(image: MemoryImage(base64Decode(e.toString()))),
color: Colors.white,
),
);
},
);
}).toList(),
),
GetList Function
getBannerList() async{
dynamic data = await getDashBoardBannerData();
print(data);
return data;
}
returns (example of return cause base64 string is too long):
['base64imgstring','base64imgstring','base64imgstring']
getBannerList method returns Future<dynamic> not List :
1- change method signature to Future<List<String>> getBannerList() async{}
2- define a variable in your StatefullWidget called
late final Future<List> bannerList;
3- initialize you variable in init state
#override
void initState(){
bannerList = getBannerList();
}
4- now we should use FutureBuilder with our basserList
FutureBuilder<List<String>>(
future: bannerList, // your future data
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.hasData) {
return CarouselSlider(
options: CarouselOptions(
height: MediaQuery.of(context).size.height * 0.15,
autoPlay: true,
autoPlayInterval: Duration(seconds: 5),
initialPage: 0,
),
items: snapshot.data.map((e) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(
image: DecorationImage(image: MemoryImage(base64Decode(e.toString()))),
color: Colors.white,
),
);
},
);
}).toList(),
);
} else if (snapshot.hasError) {
return Text('Loading Error'); // error state
} else {
return CircularProgressIndicator(); // loading state
},
you should use explicit types for fewer bugs.

Can i use if statement to switch between locations?

I hope this meet all flutter developers well.
i'm working on an app and there are several issues i've been going through. here is my question.
i want to switch between selectedLocations and it will bring out forms related to the location out for users to fill.
here are the locations below in pictures and the codes follows.
void switchSelectedCountry(selection) {
events = selection;
scoops = selection;
setState(() {
_selectedLocation = selection;
});
}
formField: FixDropdownButtonFormField(
value: _selectedLocation,
hint: Text('Select'),
items: <String>['Scoops', 'Events',].map((String value) {
return new FixDropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (newValue) {
setState(() {
_selectedLocation = newValue;
});
},
),
),
you can check which value has been selected in drop down and based on that you can show specific form,
for example,
if(dropdownValue == 'events') Column(
children: <Widget>[
TextFormField(
controller: yearController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).translate(
'year'),
hintStyle: TextStyle(fontSize: 10),
filled: true,
),
keyboardType: TextInputType.number,
),
],
),
if(dropdownValue == 'Scoops') Sizebox(RETURN YOU WIDGET)

How to unit Test a Flutter TextFormField maxlines

Is it possible to write a unit test that verifies that the maxLines property of a TextFormField is set correctly. I can not find a way to access the property:
i create a TextFormField
final field = TextFormField(
initialValue: "hello",
key: Key('textformfield'),
maxLines: 2,
);
then in the test i get access to the form field with tester.widget
final formfield =
await tester.widget<TextFormField>(find.byKey(Key('textformfield')));
but since the maxLines property is passed to the Builder which returns a Textfield, how can i get access to the textfield.
Or is there an completely other ways to verify this?
I don't know if this is a good solution but as i set the value of my TextFormField i can find the EditableText widget directly.
of this widget i can find test the property maxLines.
final EditableText formfield =
tester.widget<EditableText>(find.text('testvalue'));
expect(formfield.maxLines, 2);
The reason you can't see properties such as maxLines or maxLength is because they belong to the TextField class.
Take a look at the documentation of the TextFormField constructor in the source file:
/// Creates a [FormField] that contains a [TextField].
///
/// When a [controller] is specified, [initialValue] must be null (the
/// default). If [controller] is null, then a [TextEditingController]
/// will be constructed automatically and its `text` will be initialized
/// to [initialValue] or the empty string.
///
/// For documentation about the various parameters, see the [TextField] class
/// and [new TextField], the constructor.
Unfortunately you can't retrieve the TextField object from a TextFormField, you'll have to find the TextField object through a finder instead.
Let's assume you have a form with 2 fields - first name and last name. What you need to do is find all widgets of type TextField, add them to a list and then you can loop through each element of the list and run your test. Here's an example:
testWidgets('Form fields have the correct maximum length and number of lines',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Form(
child: Column(
children: <Widget>[
TextFormField(
key: Key('first_name'),
decoration: InputDecoration(hintText: 'First name'),
maxLines: 1,
maxLength: 50,
obscureText: true,
),
TextFormField(
key: Key('last_name'),
decoration: InputDecoration(hintText: 'Last name'),
maxLines: 1,
maxLength: 25,
),
],
),
),
),
));
List<TextField> formFields = List<TextField>();
find.byType(TextField).evaluate().toList().forEach((element) {
formFields.add(element.widget);
});
formFields.forEach((element) {
expect(element.maxLines, 1);
switch (element.decoration.hintText) {
case 'First name':
expect(element.maxLength, 50);
break;
case 'Last name':
expect(element.maxLength, 25);
break;
}
});
});
If you only one field, you could do this instead:
TextField textField = find.byType(TextField).evaluate().first.widget as TextField;
expect(textField.maxLines, 1);
expect(textField.maxLength, 50);
You can test it with an Integration test. The logic would be to type more text into the TextFormField that the one it's expected.
So we can verify the TextFormField is only allowing 2 character, as follow.
e.g. component:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SingleChildScrollView(
child: MyLoginPage(title: 'Flutter Demo Home Page'),
),
),
);
}
}
class MyLoginPage extends StatefulWidget {
MyLoginPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyLoginPageState createState() => _MyLoginPageState();
}
class _MyLoginPageState extends State<MyLoginPage> {
String _email;
String _password;
TextStyle style = TextStyle(fontSize: 25.0);
#override
Widget build(BuildContext context) {
final emailField = TextField(
key: Key('textformfield'),
obscureText: false,
maxLength: 2,
style: style,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
prefixIcon: Icon(FontAwesomeIcons.solidEnvelope),
hintText: "Email",
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red[300], width: 32.0),
borderRadius: BorderRadius.circular(97.0))),
onChanged: (value) {
setState(() {
_email = value;
});
},
);
final passwordField = TextField(
obscureText: true,
style: style,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
prefixIcon: Icon(FontAwesomeIcons.key),
hintText: "Password",
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red[300], width: 32.0),
borderRadius: BorderRadius.circular(25.0))),
onChanged: (value) {
setState(() {
_password = value;
});
},
);
return Center(
child: Column(
children: <Widget>[
Container(
color: Colors.yellow[300],
height: 300.0,
),
emailField,
passwordField,
],
),
);
}
}
The test:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_textfields_up/main.dart';
void main() {
testWidgets('Email should be only 2 characters', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
var txtForm = find.byKey(Key('textformfield'));
await tester.enterText(txtForm, '123');
expect(find.text('123'), findsNothing); // 3 characters shouldn't be allowed
expect(find.text('12'), findsOneWidget); // 2 character are valid.
});
}
Please observe I'm sending 3 character, and the TextFormField should only allow 2.
Hope this help.