A list "options" is generated from json file as below and no issue:
For simplicity, some parts were removed
Future getidea(String subject) async {
List ideaList = [];
for (var idea in ideaListTemp) {
List options = [];
options.add(idea["A"]);
options.add(idea["B"]);
options.add(idea["C"]);
ideaList.add(ideaItem(
idea["ideaText"],
idea["ideaMedia"],
idea[options],
));
}
return (ideaList);
}
class ideaItem {
final String ideaText;
final String ideaMedia;
List? options;
ideaItem(
this.ideaText,
this.ideaMedia,
this.options,
);
}
However, when I use "options" to create a Widget, this error occur.
error: The argument type 'List?' can't be assigned to the parameter type 'List'.
Widget build(BuildContext context) {
return Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(_horizontalMargin),
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
children: [
_buildOptions(widget.fromJSON.options), // this will have the error message
],
),
),
);
}
Widget _buildOptions(List option) {
List<Widget> listElevatedButton = [];
for (var i = 0; i < option.length; i++) {
//print(allOptions[i]);
listElevatedButton.add(
Builder(
builder: (context) => Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
print('Use the update function');
},
child: Text(
option[i],
),
),
),
),
);
}
return Column(children: listElevatedButton);
}
What is the best practice to solve the issue?
The variable options is being used carelessly, You need to make sure that the options has value before using it, To do this you can put a null check like so:
_buildOptions(widget.fromJSON.options) -> _buildOptions(widget.fromJSON.options ?? [])
This will give an empty list to _buildOptions function as long as options is empty and It won't build any widgets in _buildOptions function.
I hope you understand the concept here.
Your properties 'options' has type (optional) ?List, which mean it can contain List or null value. And you try use 'options' like argument in '_buildOptions' function, which need type List, not (optional) ?List.
Rewrite code like this:
Column(
children: [
_buildOptions(widget.fromJSON.options ?? []),
],
),
If the "widget.fromJSON.options" value is null, the _buildOptions will have the empty List, otherwise, it will have the value of the "widget.fromJSON.options" value.
Good luck ;)
Related
I'm trying to show a list of search results. The error is with the ListView Builder.
ListView.builder(
itemCount: searchResults.length,
itemBuilder: (context, index) {
final searchResultAccount = searchResults[index];
for (var element in searchResults[index].profile!) {
final searchResultProfile = element;
return ListTile(
leading: CircleAvatar(
backgroundImage: searchResultProfile.profileImage.isNotEmpty
? NetworkImage(
searchResultProfile.profileImage,
)
: null,
child: searchResultProfile.profileImage.isNotEmpty
? null
: const Icon(
Icons.person,
),
),
title: Text(
searchResultProfile.profileName,
),
onTap: () {
//navigate to profile
},
);
}
if (searchResults.isEmpty) {
return Text('no results');
}
},
),
I tried putting the code inside a try-catch block and even with the conditional statement above that I added in the end to return Text widget I'm still getting the same error.
How to clear this error?
It is possible that we will get null value from searchResults[index].profile. Therefore, loop may not return any ListTile widget.
Also, while using loop, it will return a list of widget. But you are returning single ListTile. You can wrap with Column.
searchResults.isEmpty may not meet the criteria and won't return Text(..). You can include an else statement and return a widget.
itemBuilder: (context, index) {
final searchResultAccount =....;
List<Widget> xWidgets = [];
for (var element in searchResults[index].profile!) {
xWidgets.add( ListTile(..));
}
if (xWidgets.isNotEmpty) {
return Column(
mainAxisSize: MainAxisSize.min,
children: xWidgets,
);
}
return Text('no results');
},
I would say that one of the parameters are being sent as null, try dugguing to find out where it is sending the error.
Also the for seem a bit inappropriate as it will only iterate once and return the first element of the profile at that index. You can achieve the same getting the first element.
I am trying to transfer data with navigator.pushNamed but I am getting error and I don't know how to solve it.
My Code;
First screen transfer data
Navigator.pushNamed(context, '/resultscreen',
arguments: <dynamic, dynamic>{
"totalCorrectGuess": totalCorrectGuess,
"imageName": imageName,
});
`Second screen use data
class _ResultScreenState extends State<ResultScreen> {
#override
Widget build(BuildContext context) {
Object? gameScreenData = ModalRoute.of(context)?.settings.arguments;
return Scaffold(
appBar: AppBar(
title: const Text(AppTextContants.appBarTitleResultScreen),
centerTitle: true,
),
body: Center(
child: Container(
margin: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
**Text('totalCorrectAnswer: \n${gameScreenData!['totalCorrectGuess']}'),**
**Image.asset('assets/images/${gameScreenData!['imageName']}.png'),**
ElevatedButton(
onPressed: () {},
child: const Text('data'),
)
],
),
),
),
);
}
}
Line of code error
Text('totalCorrectAnswer: \n${gameScreenData!['totalCorrectGuess']}'),
Image.asset('assets/images/${gameScreenData!['imageName']}.png'),
You have to cast the gameScreenData to Map
final gameScreenData = (ModalRoute.of(context)?.settings.arguments) as Map;
Update
In case you don't want to always pass arguments use this instead:
final gameScreenData = (ModalRoute.of(context)?.settings.arguments ?? {}) as Map;
Here you first check if it's not null with ??. If it is null it will use {} as default. But then you need to check in other parts of your code whether gameScreenData has necessary data or not.
Text('totalCorrectAnswer: \n${gameScreenData['totalCorrectGuess'] ?? '0'}'),
Image.asset('assets/images/${gameScreenData['imageName'] ?? 'default'}.png'),
Here the same thing as before happens. You check whether or not totalCorrectGuess and imageName fields are null and if they are assign default values to them.
If you always need the data make sure to pass it every time you route, otherwise use this.
You are passing Map, therefore you can typecast it
Map? gameScreenData = ModalRoute.of(context)?.settings.arguments as Map;
And use like
if (gameScreenData != null) ...[
Text(
'totalCorrectAnswer: \n${gameScreenData['totalCorrectGuess'] ?? "default value"}'),
if (gameScreenData['imageName'] != null)
Text('assets/images/${gameScreenData['imageName']}.png'),
],
This is the problem:
Column's children must not contain any null values, but a null value was found at index 0
I think it has something to do with the map, but i am not sure.
I am quite new to coding with dart so any help would be appreciated.
And here is the code:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: Main(),
));
int counter = 0;
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
static List<String> names = [
'name1',
'name2',
];
static List<String> difficulty = [
'easy',
'normal',
];
String currentDifficulty = difficulty[counter];
var count = names.length;
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: names.map((name) {
Container(
child: Column(
children: <Widget>[
Text(
name
),
Text(
'currentDifficulty'
),
],
),
);
counter += 1;
}).toList(),
),
);
}
}
If you want to know the index of each widget you are creating on a map function from a list I would suggest to use List.asMap().map((index, string)=> MapEntry(index, widget())).values.toList() since the map function alone does not allow to get the index
Try substituting your children code for:
names.asMap().map((index, name)=> MapEntry(index, Container(
child: Column(
children: <Widget>[
Text(
name
),
Text(
difficulty[index]
),
],
),
))).values.toList();
You are getting the errors because:
1) You are accessing the elements of your List wrongly. To access elements in a List, use the elementAt method
2) The children property of your Column is missing a return statement .
3) Instead of using a counter to iterate through the second list. You can map through the two lists using the IterableZip.
Check the code below: It solves the errors and it works fine
int counter = 0;
class MyHomePage extends StatelessWidget {
static List<String> names = [
'name1',
'name2',
];
static List<String> difficulty = [
'easy',
'normal',
];
// access elements in a list using the elementAt function
String currentDifficulty = difficulty.elementAt(counter);
#override
Widget build(BuildContext context) {
names.map((e) => print(e));
return Scaffold(
body: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// map the two lists using IterableZip and passing the two lists
children: IterableZip([names, difficulty]).map(
(element) {
// missing return statement
return Container(
child: Column(
children: <Widget>[
// access elements of your first list here
Text(element[0]),
// access elements of your second list here
Text(element[1]),
],
),
);
},
).toList(),
),
),
),
);
}
}
OUTPUT
I hope this helps.
I am trying to dynamically generate a set of widgets based on a particular condition. In this case I am trying to generate a list of RadioTiles
This is how I am trying to generate
List _listings = new List();
Widget _getListings() {
// TODO this will accept json objects in order to display the data
List listings = new List();
int i = 0;
for (i = 0; i < 5; i++) {
listings.add(
new RadioListTile<SingingCharacter>(
title: const Text('Lafayette'),
value: SingingCharacter.lafayette,
groupValue: _character,
onChanged: (SingingCharacter value) {
setState(() {
_character = value;
});
},
),
);
}
// return listings;
}
and I am trying to display this within a stateful widget like this :
return new SafeArea(
child: Column(children: <Widget>[
new Padding(
padding: const EdgeInsets.all(20.0),
child: new Text(
"Verify and Select a Single Listing?",
style: _textStyle,
),
),
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
_getListings(),
],
),
]));
The issue is that the value of listings is null due to which I am unable to display any widgets on the screen.
Any insights would be useful.
Thanks,
Edit :
If I do try to return a list this is what I see:
I am not sure if this is the best way to dynamically create widgets.
Here are some updates to your code:
Widget build(BuildContext context) {
return Scaffold(body: SafeArea(
child: Container(child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.all(20.0),
child: Text("Verify and Select a Single Listing?",),
),
Expanded(child: ListView(
padding: const EdgeInsets.all(20.0),
children: _getListings(), // <<<<< Note this change for the return type
),
)
])
)));
}
List _listings = new List();
List<Widget> _getListings() { // <<<<< Note this change for the return type
List listings = List<Widget>();
int i = 0;
for (i = 0; i < 5; i++) {
listings.add(
RadioListTile<String>(
title: const Text('Lafayette'),
value: "c",
groupValue: "x",
onChanged: (_) {
},
),
);
}
return listings;
}
Some things to consider above:
I've made changes to make the code in order to compile and be used for this answer.
added comments for notable changes
List _listings is unused
you can also drop the new keyword when creating new objects (the new version of dart is able to handle this)
Result:
Some comments on the previous answer;
Please do not use unnecessary Containers, if a Container only has a child and nothing else, remove it.
The new keyword does not have to be used, Dart linters even tell not to use it. Like here..
Also if your list does not change you could use a List.unmodifiable like in the example below.
final List<Widget> widgets = List.unmodifiable(() sync* {
for (int i = 0; i < 5; i++) {
yield RadioListTile<String>(
title: const Text('Lafayette'),
value: "c",
groupValue: "x",
onChanged: (_) {
},
);
}
}());
This can be used to avoid unnecessary for loop. Doing the same thing in 2 lines
int numberOfWidgets = 5;
List<Widget> listings = List<Widget>.filled(numberOfWidgets, buildWidget());
This will make a list with exact number of widgets.
Also, this is only helpful if you want similar type of widget in a list
I have a list of strings defined like this:
var list = ["one", "two", "three", "four"];
I want to render the values on the screen side by side using text widgets. I have attempted to use the following code to attempt this:
for (var name in list) {
return new Text(name);
}
However, when I run this code, the for loop only runs once and there is only one text widget that gets rendered that says one (the first item in the list). Additionally, when I add a log message inside my for loop, it gets triggered once as well. Why isn't my for loop looping based on the length of the list? It seems to run only once and then quit.
Basically when you hit 'return' on a function the function will stop and will not continue your iteration, so what you need to do is put it all on a list and then add it as a children of a widget
you can do something like this:
Widget getTextWidgets(List<String> strings)
{
List<Widget> list = new List<Widget>();
for(var i = 0; i < strings.length; i++){
list.add(new Text(strings[i]));
}
return new Row(children: list);
}
or even better, you can use .map() operator and do something like this:
Widget getTextWidgets(List<String> strings)
{
return new Row(children: strings.map((item) => new Text(item)).toList());
}
It is now possible to achieve that in Flutter 1.5 and Dart 2.3 by using a for element in your collection.
var list = ["one", "two", "three", "four"];
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for(var item in list ) Text(item)
],
),
This will display four Text widgets containing the items in the list.
NB. No braces around the for loop and no return keyword.
The Dart language has aspects of functional programming, so what you want can be written concisely as:
List<String> list = ['one', 'two', 'three', 'four'];
List<Widget> widgets = list.map((name) => new Text(name)).toList();
Read this as "take each name in list and map it to a Text and form them back into a List".
For googler, I wrote a simple Stateless Widget containing 3 method mentioned in this SO. Hope this make it easier to understand.
import 'package:flutter/material.dart';
class ListAndFP extends StatelessWidget {
final List<String> items = ['apple', 'banana', 'orange', 'lemon'];
// for in (require dart 2.2.2 SDK or later)
Widget method1() {
return Column(
children: <Widget>[
Text('You can put other Widgets here'),
for (var item in items) Text(item),
],
);
}
// map() + toList() + Spread Property
Widget method2() {
return Column(
children: <Widget>[
Text('You can put other Widgets here'),
...items.map((item) => Text(item)).toList(),
],
);
}
// map() + toList()
Widget method3() {
return Column(
// Text('You CANNOT put other Widgets here'),
children: items.map((item) => Text(item)).toList(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: method1(),
);
}
}
To loop through a for-loop with multiple widgets in children,
children: [
for(int i = 0; i < item.length; i++) ...[
Widget1,
Widget2,
...
],
],
The simplest way is to map your list inside a Row or a Column widget :
var list = ["one", "two", "three", "four"];
Widget build(BuildContext context) {
return Row(children: List.from(list.map((name) => Text(name))));
}
One line
Column( children: list.map((e) => Text(e)).toList() )
You can use ListView to render a list of items. But if you don't want to use ListView, you can create a method which returns a list of Widgets (Texts in your case) like below:
var list = ["one", "two", "three", "four"];
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('List Test'),
),
body: new Center(
child: new Column( // Or Row or whatever :)
children: createChildrenTexts(),
),
),
));
}
List<Text> createChildrenTexts() {
/// Method 1
// List<Text> childrenTexts = List<Text>();
// for (String name in list) {
// childrenTexts.add(new Text(name, style: new TextStyle(color: Colors.red),));
// }
// return childrenTexts;
/// Method 2
return list.map((text) => Text(text, style: TextStyle(color: Colors.blue),)).toList();
}
You can make use of ListView.Builder() if you are receiving response from a http request that as an array
List items = data;
Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (BuildContext context, int index){
return Container(
child: Text(
items[index]['property']
),
);
},
),
);
Where
data is content returned from a http request using post or get
item is the array
'property' is one of the property of each item in the array assuming your are receiving back a list of objects
An easier approach may be to use expand:
For example
var paragraphs = ['Para1','Para2','Para3'];
Somewhere in your widget tree you can do this:
...paragraphs.expand((value) => [
SizedBox(
height: 10.0,
),
Text(
value,
// Add some styling if necessary
),
SizedBox(
height: 20.0,
),
]),
Here expand returns an iterable of widgets which is then spread using the Spread Operator(...).
when you return some thing, the code exits out of the loop with what ever you are returning.so, in your code, in the first iteration, name is "one". so, as soon as it reaches return new Text(name), code exits the loop with return new Text("one"). so, try to print it or use asynchronous returns.
Below works for me using the collection package :
https://pub.dev/packages/collection
children: <Widget>[
...languages.mapIndex((idx, item) {
return InkWell(
child: CustomCheckbox(Skill(item, _languageSelections[idx])),
onTap: () {
setState(() {
_languageSelections[idx] = !_languageSelections[idx];
});
});
})
],