Generating new Cards based on List of Objects - list

I have a list of objects, is there a way to loop over a Card widget so that for each object in this list, a new card created with the attributes of this widget of interest.
The following code definitely does not work, it only serves as an illustration to show the logic behind what I am trying to achieve
//function declaration and body...
List<CardInfo> list = new List();
for (var i in cardInfo){ //cardInfo is a list of json created previously.
list.add(new CardInfo.fromJson(i));
print (list); }//tested the list and it contains the objects I want
return list;
}
...
//my widget{
#override
Widget build(BuildContext context) {
return new Dismissible(
for (i in list){
child: new Card(
child:
new ListTile(leading: new Icon(Icons.star, color: Colors.yellowAccent,),
title: new Text(list(i).field1),//field1 is a string in CardInfo
subtitle: new Text(list(i).field2),//field2 is a string in CardInfo
is this doable by any mean ?
Update
I figured out converting each JSON into object and then working with the objects directly, now _loadData() function returns an object on each iteration
_loadData() async {
var url = 'myurl';
var httpClient = createHttpClient();
var response =await httpClient.get(url);
List cardInfo = JSON.decode(response.body);
List<CardInfo> list = new List();
for ( var i in cardInfo){
var ci = new CardInfo.fromJson(i);
list.add(new CardInfo.fromJson(i));
return ci;
}
//return list;
}
....
So is there a way to set up my class constructor so that it allows me to access the fields of the returned objects ? something like this:
title: new Text(new CardInfo(_loadData()).field)
Update 2:
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _loadData(),
builder: (BuildContext context, AsyncSnapshot<List> jsonList){
if (jsonList.hasData==true){
var cardInfo = jsonList.data[0];//is this right ?
print (cardInfo);
return new Dismissible(
child: new Column(
children:
cardInfo.map(
(CardInfo info)=>
new Card(
child: new ListTile(leading: new Icon(Icons.star, color: Colors.yellowAccent,),
title: new Text(info.field),
subtitle: new Text("Subtitle"),)
) ), ),....
My function is at the top as follows:
_loadData() async {
var url = 'myurl';
var httpClient = createHttpClient();
var response = await httpClient.get(url);
List cardInfo = JSON.decode(response.body);
List<CardInfo> list = new List();
for (var i in cardInfo) {
list.add(new CardInfo.fromJson(i));
}
return list;
}
I get the following error:
Class 'CardInfo' has no instance method 'map'.

You can use the Iterable.map method to do this. For example:
new Column(
children: list.map(
(CardInfo info) => new Card(child: new Text(info.field1)),
).toList()
)

Related

List is Empty (Flutter)

I am creating a List in Flutter and displaying it in a Column, When I Run it is just Empty and when I print the list it just prints an Array
I/flutter (24613): []
I am using this code to create the List:-
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
This is My User Class:-
class User {
final String id;
final String username;
final String email;
final String photoUrl;
User({
this.id,
this.username,
this.email,
this.photoUrl,
});
factory User.fromDocument(DocumentSnapshot doc) {
return User(
id: doc.data()['id'],
username: doc.data()['username'],
email: doc.data()['email'],
photoUrl: doc.data()['photoUrl'],
);
}
}
The Code Is Showing No Errors and the Column Is not Displaying, Also When I print The length of the List it Shows it is Zero:-
I/flutter (24613): 0
What Could be The problem ??
I guess we need to tweak some of your code little bit to make the logic working. :)
builder param should be specified with Type otherwise it will be of type dynamic. To be in safer side in this case it will be QuerySnapshot. So,
builder: (context, snapshot) in your code becomes
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot).
Next, there is no need of looping through foreach and instead you can try something like below.
snapshot.data.docs.map((document) { .... }
snapshot.data.documents in your code is not valid way of getting the Firestore Documents. Please refer official doc
And you need to return a widget from builder which you have done correctly. But, by mistake you are passing the List<UserList> to Column which will be expecting List<Widget>
return Column (children: usersList);
Here I can see you are passing usersList which is of type List<UserList>. So you can replace Column with ListView or similar kind of other widget since, Column doesn't support scroll.
So combining all these bits and pieces you will get the below snippet.
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('timestamp', descending: true)
.limit(30)
.snapshots(), // Just for simplicity.
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//When there is no data returned from the firebase.
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.docs.map((document) {
return Text("Title: " + document['username']);
}).toList(),
);
},
);
For simplicity, I have returned the Text widget. You can implement your own UI there.
NOTE : This is the basic working example and you need to fine tune accordingly like using model classes instead of directly accessing based on your requirements.
Your Code
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
It is because you have to await for the json to actually get parse to the dart model. Second thing is forEach method is synchronous it doesn't wait for the async operation to complete, this is the reason why your list is empty.
This SO question has lot of different ways to make a list work asynchronously in flutter.
Column shows data before fetching data, so it shows empty list. For this use setstate according to your state management type ("notifylisteners" in provider) after getting data, so by this the screen will be updated and column also shows the updated list.
I'm not very sure how you're handling the scope of the variable.
Here's my minimal reproducible code which can give you some idea on how to add the items to the list.
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final List<Widget> _list = [FlutterLogo()];
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick >= 2) timer.cancel();
setState(() => _list.add(FlutterLogo()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: _list),
);
}
}
As 'Ashutosh patole' said, 'forEach' method does not wait iteration's complete.
I think that because of this reason, although you made a 'usersList',
there is no data when build widget in 'usersList'.
To fix this, you'd better change from 'forEach' to 'for'.
void main() async {
List<String> data = [ 'a', 'b', 'c'];
List<String> result = [];
data.forEach((data) async {
await Future.delayed(Duration(seconds: 1));
result.add(data);
});
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
print('-----------------');
result = [];
for (var item in data) {
await Future.delayed(Duration(seconds: 1));
result.add(item);
}
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
}
In your code, you can change like below.
List<UserList> usersList = [];
for (var doc in snapshot.data.documents) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
}
Before calling the data, check all fields:
Firestore Docs
Add a print() to see where the problem
FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
//This
if (snapshot.hasError) {
return Text("Something went wrong");
}
print(snapshot.data);
//This
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
print(snapshot.data);
//This
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
This is what i typically use.Try out this! Please balance the brackets in the code
FutureBuilder(
future: users.orderBy('timestamp', descending: true).limit(30),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
List<User>ulist=snapshot.data;
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(top: 25,bottom: 35),
itemCount: evlist==null?0:evlist.length,
itemBuilder: (BuildContext context, int index) {
String evtime=evlist[index].fromdate.substring(11,16);
String ontime=evlist[index].fromdate.substring(0,16);
return Container(
decoration: BoxDecoration(
border: Border.all(width: 1.8,color: Colors.indigo[900]),
borderRadius: BorderRadius.circular(12.0),
color: Colors.grey[200]
),
margin:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 4.0),
child: ListTile(
leading: Icon(Icons.notifications),
title: Text(ulist[index].username.toString()),
subtitle:Text("next data"),
),
);
},
);

Adding array of Markers to Flutter_Map only show after moving the map

Using Flutter_Map here. I am loading the markers from a local json file in my assets directory. The map draws, the markers load into my marker array, but not until after I move the map on the screen do the markers show. I'm a bit new to flutter here, so I must be doing something wrong in the marker loading part?
I have tried a few things. Currently this is my flutter map and marker array code:
class _MapWidget extends StatelessWidget {
//const so flutter doesn't rebuild it each time...
const _MapWidget();
// final SiteID sid = null;
// final Sites site = null;
#override
Widget build(BuildContext context) {
print("add map");
return new FlutterMap(
options: new MapOptions(
center: new LatLng(40.0, -120.0),
zoom: 8.0,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
subdomains: ['a', 'b', 'c'],
tileProvider: CachedNetworkTileProvider(),
),
new MarkerLayerOptions(markers: _MapDataState().marker),
],
);
}
}
Loading marker array on _MapDataState() constructor seems to be the only place i can get the array to share into my _MapWidget:
class _MapDataState extends State<MapData> {
List<SitesList> sl = [];
List<SnoTelSite> sts = [];
var marker = <Marker>[];
_MapDataState(){
print("map data state");
loadSites().then((sitesdata) {
print('Loaded Sites Asset JSON');
//clone sitesdata into sts array
sts..addAll(sitesdata);
sts.forEach((s) {
marker.add(
Marker(
point: new LatLng(double.parse(s.lat),double.parse(s.lng)),
builder: (ctx) => _MarkerPopUp(sitename: s.name, siteelevation: s.elevation, siteid: s.siteID,),
),
);
});
});
}
// #override
// initState() {
// print("INIT STATE");
// super.initState();
// }
//local load assets ... constants hold path snotelsitesjson
Future<String> _loadSiteAssets() async {
return await rootBundle.loadString(snotelsitesjson);
}
Future loadSites() async {
String jsonString = await _loadSiteAssets();
final jsonResponse = json.decode(jsonString);
SitesList sitesList = new SitesList.fromJson(jsonResponse);
return sitesList.sites;
}
//main build and screen layout
#override
Widget build(BuildContext context) {
print("BUILD LAYOUT");
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.cyan,
),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
leading: Icon(Icons.ac_unit),
title: Text("SnoTel Map"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
debugPrint("Favorites");
},
),
IconButton(
icon: Icon(Icons.feedback),
onPressed: () {
debugPrint("Message sent");
},
),
],
),
body: _MapWidget()),
);
}
}
Update! After the setState() help from #Ademir below, this is working to show my markers on load:
Update setState():
setState(() {
marker = marker;
});
Change in _MapWidget:
const _MapWidget({this.marker});
final List<Marker> marker;
...
body: _MapWidget(marker: marker)),
First, change this:
_MapDataState(){
loadSites().then((sitesdata) {
print('Loaded Sites Asset JSON');
//clone sitesdata into sts array
sts..addAll(sitesdata);
sts.forEach((s) {
marker.add(
Marker(
point: new LatLng(double.parse(s.lat),double.parse(s.lng)),
builder: (ctx) => _MarkerPopUp(sitename: s.name, siteelevation: s.elevation, siteid: s.siteID,),
),
);
});
});
}
from the constructor to the initState():
#override
initState() {
print("INIT STATE");
super.initState();
loadSites().then((sitesdata) {
print('Loaded Sites Asset JSON');
//clone sitesdata into sts array
sts..addAll(sitesdata);
sts.forEach((s) {
marker.add(
Marker(
point: new LatLng(double.parse(s.lat),double.parse(s.lng)),
builder: (ctx) => _MarkerPopUp(sitename: s.name, siteelevation: s.elevation, siteid: s.siteID,),
),
);
});
});
}
After that, add a setState((){}); here:
sts.forEach((s) {
marker.add(
Marker(
point: new LatLng(double.parse(s.lat),double.parse(s.lng)),
builder: (ctx) => _MarkerPopUp(sitename: s.name, siteelevation: s.elevation, siteid: s.siteID,),
),
);
});
setState((){});
Out of curiosity to understand your code , what's in MapData class ? and in _MarkerPopUp ?
I'm very new to flutter and dart, and I try to understand. Is your complete code available somewhere ?
Thanks,

Flutter: `notifyListeners` not updating the list

I am trying to list categories of products in my app, and i am using provider package for state management. During the build time its shows the list is empty even though it is not, then I add a click event to update the list that time it works.
I call the getAllCategories() function during the splash screen and the vallCategoryList has values.
This is my category class
class Category with ChangeNotifier{
int catId;
String catName;
String catThumbnail;
List<SetCategory> allCategoryList = [];
void getAllCategories() async {
String categoryUrl = 'https://app.ecwid.com/api/ccccccc';
Response allCategory = await get(categoryUrl);
print('getAllCategories');
if (allCategory.statusCode == 200) {
var categoryData = allCategory.body;
int totalcount = jsonDecode(categoryData)['count'];
if (allCategoryList.length != totalcount) {
allCategoryList.clear();
for (int i = 0; i < totalcount; i++) {
allCategoryList.add(SetCategory(
catId: jsonDecode(categoryData)['items'][i]['id'],
catName: jsonDecode(categoryData)['items'][i]['name'],
catThumbnail: jsonDecode(categoryData)['items'][i]['thumbnailUrl'],
));
}
}
}
print('allcategorylist length ${allCategoryList.length}');
notifyListeners();
}
}
class SetCategory {
int catId;
String catName;
String catThumbnail;
SetCategory(
{ this.catId, this.catName, this.catThumbnail});
}
My code for screen
class HomeScreen extends StatefulWidget {
static const String id = 'homeScreen';
// static int reload = 0;
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final category = Provider.of<Category>(context);
print('category length ${category.allCategoryList.length}'); // here it shows length as 0 even though it has a value of 16.
return Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Category ${category.allCategoryList.length}',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
textAlign: TextAlign.start,
),
),
InkWell(
onTap: () {
category.getAllCategories(); // when i tap here this updates the list
}),
],
),
Did you use ChangeNotifierProvider in your Widget as shown here
If you just used Provider it is not updating but just makes the object accessible from the descendants
Solved by adding a Consumer, changed code like this
child: Consumer<Category>(
builder: (_,category,__){
return ListView.builder();
}

flutter : Unsupported operation: Cannot add to an unmodifiable list

I have a ListView inside a StatelessWidget. It has items and every item contains a checkbox. When someone checks an item, I want the ListView to send this as a parameter to another page. But when I do that, it's giving me this error:
I/flutter ( 7067): The following UnsupportedError was thrown while handling a gesture:
I/flutter ( 7067): Unsupported operation: Cannot add to an unmodifiable list
I/flutter ( 7067): When the exception was thrown, this was the stack:
and this is my code
class StudentsList extends StatelessWidget {
final List<Child> mList;
StudentsList({this.mList});
#override
Widget build(BuildContext context) {
List<Child> selectedList = [];
return Container(
margin: EdgeInsets.only(top: 50, bottom: 20),
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: mList == null ? 0 : mList.length,
padding: EdgeInsets.only(right: 10),
itemBuilder: (BuildContext context, int position) {
return GestureDetector(
onTap: () {
if (selectedList.isEmpty) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => SolokPage(
mChildList: [mList[position]],
isTeacher: true,
),
),
);
} else {
if (!selectedList.contains(mList[position])) {
selectedList.add(mList[position]);
}
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => SolokPage(
mChildList: selectedList,
isTeacher: true,
),
),
);
}
},
child: StudentItem(
student: mList[position],
),
);
},
),
);
}
}
Stateless Widget properties are meant to be immutable
class StudentsList extends StatelessWidget {
// final means, flutter will not change value in future
final List<Child> mList;
StudentsList({this.mList});
Why ?
Because Flutter expects no business logic resides in StatelessWidget.
If we need to add new Student in Student list, it is considered as business logic.
If we need to delete some Student in Student list, it is considered as business logic.
So by using stateless widget, Flutter will only focuses on How it will be displayed on Screen, what is the width, the constraints and etc.
That's why we found final syntaxes before class properties in StatelessWidget.
Similiar to our college life. Our Grades that marked in final report, will not change even after we graduate from university. As it said to be in Final Report, then it must be final.
Stateful Widget properties are mutable
Why ?
Because flutter expects business logic resides in StatefulWidget.
Changes to be made
So I suggest to change StudentsList Widget, from this :
class StudentsList extends StatelessWidget {
final List<Child> mList; // this is the issue
StudentsList({this.mList});
to this one :
class StudentsList extends StatefulWidget {
#override
_StudentsListState createState() => _StudentsListState();
}
class _StudentsListState extends State<StudentsList> {
// final List<Child> mList; // Do not mark this as final
List<Child> mList;
...
}
Working Repository
You may look working repository that is closely-related to your issue. Github
Stateless Widgets property cannot be immutable means in simple words is that it should not contain any non-final variables.
Simply convert it to Stateful widget and inside the class _StudentsListState create your variable WITHOUT the final keyword because you are modifying the value of that List.
If you want to keep stateless (IE you just need to return some data or maybe youre using hooks) you could also try toList() to create a copy, then modify, then replace the original list
I encountered this problem in a simple function, and I solved it like this.
Future<void> createProduct({required Product product, required List<File> images}) async {
for (final image in images) {
final imageId = const Uuid().v4();
final compressedimage = await ImageCompress.instance.compressFile(image);
final taskSnapShot = await StorageService.instance.uploadProductPhoto(
file: compressedimage,
productId: product.productId,
childUUID: imageId,
);
final downloadURL = await taskSnapShot.ref.getDownloadURL();
product.imagesUrl.add(downloadURL);
// sendProduct.addImages(downloadURL: downloadURL);
}
await _collection.doc().set(
product.toMap(),
);
}
turn it into this.
Future<void> createProduct({required Product product, required List<File> images})
async {
List<String> newUrls = [];
for (final image in images) {
final imageId = const Uuid().v4();
final compressedimage = await ImageCompress.instance.compressFile(image);
final taskSnapShot = await StorageService.instance.uploadProductPhoto(
file: compressedimage,
productId: product.productId,
childUUID: imageId,
);
final downloadURL = await taskSnapShot.ref.getDownloadURL();
newUrls.add(downloadURL);
// sendProduct.addImages(downloadURL: downloadURL);
}
final sendProduct = product.copyWith(imagesUrl: newUrls );
await _collection.doc().set(
sendProduct.toMap(),
);
}

Only one item is appearing at a time on list view

I cant seem to figure out how to get all of my items in my list to display in the list view
Currently, when I click my button to display the list, only one items shows up. If I click back, and click main button again, it shows 2 items from the list. Rinse and repeat, 3 items. I can't seem to debug with print statements to see where my error lies. When I try print(trails) or other variations, it says Instance of trail model (not very helpful). Any ideas?
Here's my code:
class HomeScreen extends State<MyApp> {
int counter = 0;
Future<List<TrailModel>> fetchData() async {
counter++;
var response = await get(
'https://www.hikingproject.com/data/get-trails?lat=39.733694&lon=-121.854771&maxDistance=10&key=200419778-6a46042e219d019001dd83b13d58aa59');
final trailModel = TrailModel.fromJson(json.decode(response.body));
//trails.add(trailModel);
setState(() {
trails.add(trailModel);
});
return trails;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("HikeLocator")),
body: new RaisedButton(
child: Text("click me"),
onPressed: () async {
final trails = await fetchData();
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new ListScreen(trails)),
);
},
),
));
}
}
class ListScreen extends StatelessWidget {
final List<TrailModel> trails;
ListScreen(this.trails);
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Here are your trails"),
),
body: TrailList(trails),
);
}
}
class TrailList extends StatelessWidget {
final List<TrailModel> trails;
TrailList(this.trails);
Widget build(context) {
return ListView.builder(
itemCount: trails.length,
itemBuilder: (context, int index) {
Object myText = json.encode(trails[index].trails);
List<dynamic> myText2 = json.decode(myText);
return Text(myText2[index]['name']);
},
);
}
}
class TrailModel {
Object trails;
TrailModel(this.trails);
TrailModel.fromJson(Map<String, dynamic> parsedJson) {
trails = parsedJson['trails'];
}
}
I think my problem might lie in the fetchData(), but I'm not entirely sure. Trying to at least print out the values to limit where my problem might be. (Is it only adding 1 to the list each time I press the button? Is it only rendering one when I click it? Is it fetching all the data each click or only one json object? etc.)
Thank you kindly for any assistance. Sorry, I'm kind of new to dart, so this is a huge learning curve for
There are a couple problems in your code. The main reason this doesn't work as you expect is because you're parsing all elements of the json into one TrailModel object, but then your code assumes that you'll have multiple TrailModel objects.
The easiest way to fix it up and get it working is to use the list from TrailModel.trails instead of the one in the widget.
First, in ListScreen, pass just the first element in the list.
class ListScreen extends StatelessWidget {
final List<TrailModel> trails;
...
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
...
body: TrailList(trails.first),
);
}
}
Then, in TrailList, use the trails list you have from TrailModel:
class TrailList extends StatelessWidget {
final TrailModel model;
TrailList(this.model);
Widget build(context) {
return ListView.builder(
itemCount: model.trails.length,
itemBuilder: (context, int index) {
final trail = model.trails[index];
...
},
);
}
}
When I try print(trails) or other variations, it says Instance of trail model (not very helpful)
print uses the output of the toString method in your classes. You're seeing Instance of trail model because that's the default implementation you get from the super class Object. You can override it to get something more useful:
class TrailModel {
#override
String toString() => 'trails=$trails';
}