Dynamically generate widgets in Flutter - flutter

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

Related

Flutter/Dart: Create/assign dynamic List

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 ;)

How to get data from list of map to display in Recordable list view

Please please help me ..
I found this code from a software site, but only used list of string but I have List tasks; so I can't view the data of List tasks;
In the Recordable list view and also the value of the key in the Recordable list view I didn't understand it..Does anyone have an idea to solve this?
final List<String> _products =
List.generate(100, (index) => "Product ${index.toString()}");
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Kindacode.com'),
),
body: ReorderableListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final String productName = _products[index];
return Card(
key: ValueKey(productName),
color: Colors.amberAccent,
elevation: 1,
margin: const EdgeInsets.all(10),
child: ListTile(
contentPadding: const EdgeInsets.all(25),
title: Text(
productName,
style: const TextStyle(fontSize: 18),
),
trailing: const Icon(Icons.drag_handle),
onTap: () {/* Do something else */},
),
);
},
// The reorder function
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex = newIndex - 1;
}
final element = _products.removeAt(oldIndex);
_products.insert(newIndex, element);
});
}),
);
}
}
I found this code from a software site
If you're new to Flutter and programming in general, while it's good to read code, it might be not a good idea to just copy and paste code. It would be good to actually understand it and also to understand the programming language you're using. I suggest reading this.
but only used list of string but I have List tasks
You didn't provide what your "Task" class is, so I can just help you with some pseudocode. In your ReordableListView's itemBuilder function you have to extract your data, somehow:
final yourTask = _products[index];
final yourTaskName = yourTask.name; // example
Unluckily without further info this is the best I can do

Flutter provider consumer removes my items

I'm trying to build a sort function in order to sort JSON data.
For this, I have a button that opens a "showModalBottomSheet".
Within it I can choose the following data of the school class numbers.
So in my data I have 6 classrooms when loading in my constructor.
My filter is represented by buttons which are active or not if the filter contains the number of the classroom. My code works pretty much, my problem is that when I select a filter button in order to activate or not the filter, the button is deleted instead of staying but changing color
My notifier :
class TablesNotifier with ChangeNotifier {
// Services
// ---------------------------------------------------------------------------
final jsonSelectorService = locator<JsonSelectorService>();
// Variables
// ---------------------------------------------------------------------------
//all data from my classerooms in JSON
List<ClassroomModel> classrooms;
// Data that I will display and reconstruct based on my filter parameters
List<ClassroomModel> classroomsFiltered;
List<int> numberOfClassrooms = List();
// Model which will store the parameters of my filters and as a function I will load the data to display
FilterClassroomsModel filterClassroomsModel = FilterClassroomsModel();
// Constructor
// ---------------------------------------------------------------------------
TablesNotifier(){
_initialise();
}
// Initialisation
// ---------------------------------------------------------------------------
Future _initialise() async{
classrooms = await jsonSelectorService.classrooms('data');
classroomsFiltered = classrooms ;
// I install the number of existing classrooms
// Here the result is [1,2,3,4,5,6]
classrooms.forEach((element) {
if(!numberOfClassrooms.contains(element.type)){
numberOfClassrooms.add(element.type);
}
});
// I install the number of classrooms activated by default in my filter
// As I decide to display all my classrooms by default
// My filter on the classrooms must contain all the loaded classrooms
filterClassroomsModel.classrooms = numberOfClassrooms;
notifyListeners();
}
// Functions public
// ---------------------------------------------------------------------------
void saveClassroomsSelected(int index)
{
// Here my classroom model also contains the numbers of the classrooms that I want to filter
if(filterClassroomsModel.classrooms.contains(index)){
filterClassroomsModel.classrooms.remove(index);
}else{
filterClassroomsModel.classrooms.add(index);
}
notifyListeners();
}
}
I have identified that in my function initialize () if I change my code by this it works :
filterClassroomsModel.classrooms= numberOfClassrooms; // this
filterClassroomsModel.classrooms= [1,2,3,4,5,6]; // By this
I am losing the dynamic side of my classroom calculation and that does not suit me. But I don't understand this behavior.
My view :
class TableScreen extends StatelessWidget {
final String title;
TableScreen({Key key, #required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MenuDrawerComponent.builder(context),
appBar: AppBar(
backgroundColor: AppColors.backgroundDark,
elevation: 0,
centerTitle: true,
title: Text(title),
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.sort),
label: Text('Filter'),
onPressed: () async{
slideSheet(context);
},
backgroundColor: AppColors.contrastPrimary,
),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context)
{
var _tableProvider = Provider.of<TablesNotifier>(context);
if(_tableProvider.chargesFiltered == null){
return Center(
child: CircularProgressIndicator(
backgroundColor: AppColors.colorShadowLight,
),
);
}else{
return Column(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(top: 10, right : 20, left : 20),
child: ListView.builder(
itemCount: _tableProvider.classroomsFiltered.length,
itemBuilder: (context, index){
return Container(
child: Column(
children: [
Row(
// Some classrooms data
),
],
),
);
},
),
)
),
],
);
}
}
void slideSheet(BuildContext context) {
var _tableProvider = Provider.of<TablesNotifier>(context, listen:false);
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
builder: (context) {
return Wrap(
children: [
Container(
color: Color(0xFF737373),
child: Container(
child: Column(
children: <Widget>[
// Some filters ...
// Here I want to rebuild the list of button for show the changes
ChangeNotifierProvider.value(
value: _tableProvider,
child: Consumer<TablesNotifier>(
builder: (context, model, child){
return _listOfClassrooms(context);
}
),
),
],
),
),
),
]
);
});
}
Widget _listOfClassrooms(BuildContext context){
var _tableProvider = Provider.of<TablesNotifier>(context);
List<Widget> list = List<Widget>();
var listClassrooms = _tableProvider.numberOfClassrooms;
var filterClassrooms = _tableProvider.filterClassroomsModel.classrooms;
for (var i = 0; i < listClassrooms.length; i++) {
int selectIndex = 0;
if(filterClassrooms.contains(listClassrooms[i])){
selectIndex = listClassrooms[i];
}
list.add(
RadioComponent(
text: "${listClassrooms[i]}",
index: listClassrooms[i],
width: (MediaQuery.of(context).size.width - 56) /3,
selectedIndex: selectIndex,
onPressed: _tableProvider.saveChargesSelected,
),
);
}
return Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 8.0, // gap between lines
children: list
);
}
}
My FilterClassroomsModel :
class FilterClassroomsModel {
int order;
int sort;
List<int> classrooms;
FilterClassroomsModel ({
this.order = 0,
this.sort = 0,
this.classrooms = const[],
});
#override
String toString() {
return '{ '
'${this.order}, '
'${this.sort}, '
'${this.classrooms}, '
'}';
}
}
EDIT : Resolved topic. Thanks to Javachipper.
In the notifier I replace that :
filterClassroomsModel.classrooms = numberOfClassrooms;
By that :
filter.classrooms = List<int>();
filter.classrooms.addAll(numberOfClassrooms);
change this:
filterClassroomsModel.classrooms = numberOfClassrooms;
to:
filterClassroomsModel.classrooms.addAll(numberOfClassrooms);
Update (you can also do it like this):
filterClassroomsModel.classrooms= new List<int>();
filterClassroomsModel.classrooms.addAll(numberOfClassrooms);

Flutter how to do for inside for loop with list widget?

i need to do for inside for loop inside widgets. i create two widgets and i called it form another widget. it says "Not in range 0..6, inclusive:7"
Code :
List<Widget> ListMyClips(i) {
List<Widget> list = new List();
var lengthItems = widget.data["recommended"][7]["blocks"][i]["items"].length;
for (var c = 0; c <= lengthItems; i++) {
list.add(
ClipRRect(
borderRadius: new BorderRadius.circular(100.0),
child: Image.network(
first[widget.data["recommended"][7]["blocks"][i]["items"][c]
["id"]]["image"]["full"],
width: 56,
),
),
);
}
return list;
}
List <Widget> ListMyWidgets() {
var lengthBlocks = widget.data["recommended"][7]["blocks"].length;
List <Widget> list = new List();
for (var i = 0; i <= lengthBlocks; i++) {
list.add(ListTile(
trailing: Icon(FontAwesomeIcons.chevronRight),
title: Row(
children: ListMyClips(i),
),
));
}
return list;
}
what i need is, dynamically for inside for loop which it will create list of list tiles.
With Dart 2.3 new features, you can use a for inside a collections, to minimize the boilerplate code and make it more legible.
Before Dart 2.3
Row(
children: <Widget>[
europeanCountries
.map((country) => Text("New $country"))
.toList();
],
)
With Dart 2.3
Row(
children: <Widget>[
for (var country in europeanCountries) Text("New ${country.data}")
],
)
Check more features that could help you in here: What's new in Dart 2.3
PS: Change your functions name to a camelCase form. This way, you should be creating a class instead of a method

Iterating through a list to render multiple widgets in Flutter?

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];
});
});
})
],