Flutter/dart accessing listdata within another list - flutter

I have a JSON response from RestApi like this:
[{"_id":"5eee4b3630cff64ee216e4fb",
"assignee_user_id":"5eab4a435647780af311d3a7",
"task_name":"Another Test work",
"task_description":"Test Description",
"assignee_name":"Test Assignee",
"status":"assigned",
"assignment_date":"20-06-2020 11:15",
"assignor_name":"Test Assignor",
"assignor_remarks":[{"commentTime":"21-06-2020 05:17","comment":"Test Comment"}]}]
and the podo class build is like this:
import 'dart:convert';
List<Work> workFromMap(String str) => List<Work>.from(json.decode(str).map((x) => Work.fromMap(x)));
String workToMap(List<Work> data) => json.encode(List<dynamic>.from(data.map((x) => x.toMap())));
class Work {
Work({
this.id,
this.assigneeUserId,
this.taskName,
this.taskDescription,
this.assigneeName,
this.status,
this.assignmentDate,
this.assignorName,
this.assignorRemarks,
});
String id;
String assigneeUserId;
String taskName;
String taskDescription;
String assigneeName;
String status;
String assignmentDate;
String assignorName;
List<AssignorRemark> assignorRemarks;
factory Work.fromMap(Map<String, dynamic> json) => Work(
id: json["_id"],
assigneeUserId: json["assignee_user_id"],
taskName: json["task_name"],
taskDescription: json["task_description"],
assigneeName: json["assignee_name"],
status: json["status"],
assignmentDate: json["assignment_date"],
assignorRemarks: List<AssignorRemark>.from(json["assignor_remarks"].map((x) => AssignorRemark.fromMap(x))),
);
Map<String, dynamic> toMap() => {
"_id": id,
"assignee_user_id": assigneeUserId,
"task_name": taskName,
"task_description": taskDescription,
"assignee_name": assigneeName,
"status": status,
"assignment_date": assignmentDate,
"assignor_remarks": List<dynamic>.from(assignorRemarks.map((x) => x.toMap())),
};
}
class AssignorRemark {
AssignorRemark({
this.commentTime,
this.comment,
});
String commentTime;
String comment;
factory AssignorRemark.fromMap(Map<String, dynamic> json) => AssignorRemark(
commentTime: json["commentTime"],
comment: json["comment"],
);
Map<String, dynamic> toMap() => {
"commentTime": commentTime,
"comment": comment,
};
}
and my api call is like this:
import './work.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
Future<List<Work>> fetchWork() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final userid = prefs.getString('user_id');
final response =
await http.get("https://myserver/api/work-monitor/work/?id=$userid",
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
print(response.body);
return workFromMap(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load Profile');
}
}
and my screen file content is like this:
class WorkMonitor extends StatefulWidget {
#override
_WorkMonitorState createState() => _WorkMonitorState();
}
class _WorkMonitorState extends State<WorkMonitor> {
Future<Work> futureMyWorkMonitor;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Work Monitor'),
),
body: Container(
child: FutureBuilder(
future: fetchWork(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, index) {
Work work = snapshot.data[index];
// return Text('${work.taskName}');
return Container(
padding: const EdgeInsets.all(5),
width: double.infinity,
child: Card(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Subject: ',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
)),
Text(
'${work.taskName}',
style: TextStyle(
fontSize: 20, color: Colors.black),
),
]),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Description: ',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
Flexible(
child: Text(
'${work.taskDescription}',
)),
]),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Assigned Date: ',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
Text(
'${work.assignmentDate}' ?? " ",
style: TextStyle(fontSize: 16),
),
]),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Description: ',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
Text(
'${work.status}' ?? "Gone",
style: TextStyle(fontSize: 16),
),
]),
Text(
'${work.assignorRemarks[index]}'
),
]),
),
),
);
},
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
);
}
}
Only issue I am facing here is, I am not able to access/iterate over comments. How I can do this?

You need to somehow also iterate through each comment inside each list item. One way (probably only for a small amount of remarks), would be a Column (or if you need scrolling you could use a SingleChildScrollView with a Column). Example:
Column(
children: work.assignorRemarks.map<Text>((remark) {
return Text(remark.comment);
}).toList()
)
If you have a lot of items you are probably better of using a nested ListView

Related

How to show comments/details of the post according to the post id from an API?

I am trying to retrieve "comments" data from the API. But the main problem is I want to see the comments of the post according to the post I've selected or pressed instead of providing a static postId as the query parameter.
Here's my code:
Comment Model
class Comment {
int postId;
int id;
String name;
String email;
String body;
Comment({
required this.postId,
required this.id,
required this.name,
required this.email,
required this.body,
});
factory Comment.fromJson(Map<String, dynamic> json) {
return Comment(
postId: json['postId'],
id: json['id'],
name: json['name'],
email: json['email'],
body: json['body'],
);
}
}
Comment Provider (Riverpod and Dio used). Note that I've used postId = 1 as query parameter
final commentProvider = StateNotifierProvider<CommentProvider, List<Comment>>(
(ref) => CommentProvider());
List<Comment> commentList = [];
class CommentProvider extends StateNotifier<List<Comment>> {
CommentProvider() : super([]) {
getComments(postId: 1);
}
Future<void> getComments({required int postId}) async {
final dio = Dio();
try {
final response = await dio.get(Api.commentApi, queryParameters: {
'postId': postId,
});
state = (response.data as List).map((e) => Comment.fromJson(e)).toList();
commentList = state;
print(response.data);
} on DioError catch (err) {
print(err);
}
}
}
This is my main screen where I'm fetching the posts data from the API
Consumer(
builder: (context, ref, child) {
final posts = ref.watch(postProvider);
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final data = posts[index];
return Container(
child: Column(
children: [
InkWell(
onTap: () {
Get.to(() => DetailScreen(), transition: Transition.zoom);
},
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(AppPadding.p16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 13,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(data.userId.toString(), style: TextStyle(color: Colors.black, fontSize: 13),),
],
),
),
Text('#' + data.id.toString(), style: TextStyle(color: Colors.black, fontSize: 13),),
],
),
Text(
data.title.toString(),
style: getSemiBoldStyle(color: Colors.black),
),
Text(
data.body.toString(),
style: getRegularStyle(color: Colors.grey).copyWith(fontSize: FontSize.s14),
maxLines: 3,
overflow: TextOverflow.fade,
),
],
),
),
),
This is where I want to show the Comments data of the post according to the post id provided
Consumer(
builder: (context, ref, child) {
final posts = ref.watch(commentProvider);
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final data = posts[index];
return Container(
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(AppPadding.p16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
// Text(
// data.email.toString(),
// style: TextStyle(
// color: Colors.black, fontSize: 13),
// ),
// Text(
// '#' + data.id.toString(),
// style: TextStyle(
// color: Colors.black, fontSize: 13),
// ),
// ],
// ),
// Text(
// data.body.toString(),
// style: getSemiBoldStyle(color: Colors.black),
// ),
// Text(
// data.body.toString(),
// style: getRegularStyle(color: Colors.grey)
// .copyWith(fontSize: FontSize.s14),
// maxLines: 3,
// overflow: TextOverflow.fade,
// ),
],
),
),
I know how to pass the post Id from one screen to another but I got no idea how to show the comments according to the passed post id using a provider. For example: when I press on post with a post id with 3, I want to see the comments of the post id 3 in the next page. At the moment, I am just able to show the comments by providing static post id value.
If you didn't understand my problem then please feel free to ask. I'll kindly elaborate. Any state management solution can be provided but Flutter Riverpod would be much appreciated.
The API was taken from here:
Posts data : (https://jsonplaceholder.typicode.com/posts)
Comments data :(https://jsonplaceholder.typicode.com/posts/{post_id}/comments) or
Alternative (https://jsonplaceholder.typicode.com/comments?postId=1)
You can use the modifier .family. For example:
final commentsProvider = FutureProvider.family<List<Comment>, int>((ref, postId) async {
final dio = Dio();
try {
final response = await dio.get(Api.commentApi, queryParameters: {
'postId': postId,
});
return (response.data as List).map((e) => Comment.fromJson(e)).toList();
} on DioError catch (err) {
print(err);
return [];
}
return [];
});
In widget:
Consumer(
builder: (context, ref, child) {
...
final comments = ref.watch(commentsProvider(postId));
...
}
)
You can read more here: family modifier

Change the id in the URL according to the selected card

Up!
I am a beginner in Flutter and I am making an application in which I have a first "boats" route. I access to the list of my boats contained in "Card" thanks to this route. Then, I have a second route, accessible from a button in each of these "Cards" allowing me to access the details of each boat.
To retrieve my data, I use a fake API to perform my tests (mockapi.io). For the list of boats, the route is "/boats". Then for the details of each boat, it becomes "/boats/:id/details". Here is my problem, how do I handle the fact that the id changes depending on the boat selected?
Here is the method to access the API:
Future<List<ListDetails>?> getListDetails({required id}) async
{
var client = http.Client();
var uri = Uri.parse('https://63e21b26109336b6cbff9ce9.mockapi.io/api/v1/boats_control/$id/details');
var response = await client.get(uri);
if(response.statusCode == 200)
{
var json = response.body;
return listDetailsFromJson(json);
}
}
And here is my boat_details view, where I try to display the API data:
class ControlListDetailViewState extends State<ControlListDetailView> {
#override
Widget build(BuildContext context) {
late final id = ModalRoute.of(context)?.settings.arguments;
List<ListDetails>? listDetails;
var isLoaded = false;
print(listDetails);
getData() async {
listDetails = await RemoteService().getListDetails(id: id);
if (listDetails != null) {
setState(() {
isLoaded = true;
});
}
}
#override
void initState() {
super.initState();
getData();
}
And here is my model :
import 'dart:convert';
List<BoatsControl> boatsControlFromJson(String str) => List<BoatsControl>.from(json.decode(str).map((x) => BoatsControl.fromJson(x)));
String boatsControlToJson(List<BoatsControl> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class BoatsControl {
BoatsControl({
required this.model,
required this.location,
required this.id,
});
String model;
String location;
String id;
factory BoatsControl.fromJson(Map<String, dynamic> json) => BoatsControl(
model: json["model"],
location: json["location"],
id: json["id"],
);
Map<String, dynamic> toJson() => {
"model": model,
"location": location,
"id": id,
};
}
Finally, here is my widget where I use this model:
Widget build(BuildContext context) {
print('ControlViewState - build');
return Scaffold(
drawer: NavDrawableWidget(), // Hamburger menu
bottomNavigationBar: FooterWidget(), // Footer Copyright
appBar: AppBar(
title: Text("${AppSettings.APP_NAME} | ${AppSettings.APP_VERSION}",
style: TextStyle(fontSize: 16)),
),
body: Column(
children: [
Text('\n${AppSettings.strings.controlTitle}',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)
),
Visibility(
visible: isLoaded,
replacement: const Center(
child: CircularProgressIndicator(),
),
child: const Text(''),
),
if(isLoaded)
Expanded(
child: ListView.builder(
itemCount: boatsControl?.length ?? 0,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: Colors.black12, width: 2),
),
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
ListTile(
title: Text('${boatsControl![index].id} | ${boatsControl![index].model}'),
leading: Icon(Icons.anchor),
),
Row(
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(boatsControl![index].location,
style: TextStyle(fontSize: 14)),
],
),
),
Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
CustomFloatingActionControlButton(
onPressed: () =>
{
Navigator.pushNamed(
context, '/controlListDefect',
arguments: boatsControl![index].id
)
},
tag: 'listDefectButton',
icon: Icons.equalizer,
),
],
),
),
],
),
],
),
),
),
);
},
),
)
]
),
);
}
Thank you in advance if you take the time to answer me and help me.

Null Check operator used on a null value

when click the showModalBottomSheet it's show on the screen
Null Check operator used on a null value
can anyone help to figure out which is the code error? kindly refer full code as below, TQ!
class ProductContentFirst extends StatefulWidget {
final List _productContentList;
const ProductContentFirst(this._productContentList, {Key? key})
: super(key: key);
#override
State<ProductContentFirst> createState() => _ProductContentFirstState();
}
class _ProductContentFirstState extends State<ProductContentFirst> {
ProductContentItem? _productContent;
List _attr = [];
#override
void initState() {
super.initState();
_productContent = widget._productContentList[0];
this._attr = this._productContent!.attr!;
//print(this._attr);
}
List<Widget>? _getAttrItemWidget(attrItem) {
List<Widget> attrItemList = [];
attrItem.list.forEach((item) {
attrItemList.add(Container(
margin: EdgeInsets.all(5),
child: Chip(
label: Text("${item}"),
padding: EdgeInsets.all(10),
),
));
print (item);
});
}
List<Widget>? _getAttrWidget() {
List<Widget> attrList = [];
this._attr.forEach((attrItem) {
attrList.add(Wrap(
children: [
Container(
width: ScreenAdapter.width(80.0),
child: Padding(
padding: EdgeInsets.only(top: ScreenAdapter.height(30.0)),
child: Text(
"${attrItem.cate}",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
Container(
width: ScreenAdapter.width(600.0),
child: Wrap(
//children: [],
children: _getAttrItemWidget(attrItem)!,
),
)
],
));
print(attrItem.cate);
});
//return null;
}
_attrBottomSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return GestureDetector(
onTap: () {
false;
},
child: Stack(
children: [
Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
Column(
children: _getAttrWidget()!,
)
],
),
),
Positioned(
bottom: 0,
width: ScreenAdapter.width(750.0),
height: ScreenAdapter.height(100),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
child: JdButton(
color: Color.fromRGBO(253, 1, 0, 0.9),
text: "Add Cart",
cb: () {
print("Add Cart");
},
),
)),
Expanded(
flex: 1,
child: Container(
child: JdButton(
color: Color.fromRGBO(255, 165, 0, 0.9),
text: "Buy",
cb: () {
print("Buy");
},
),
)),
],
))
],
),
);
});
}
#override
Widget build(BuildContext context) {
String pic = Config.domain + this._productContent!.pic!;
pic = pic.replaceAll("\\", "/");
return Container(
padding: EdgeInsets.all(10.0),
child: ListView(
children: [
AspectRatio(
aspectRatio: 16 / 12,
child: Image.network(
"${pic}",
fit: BoxFit.cover,
),
),
Container(
padding: EdgeInsets.only(top: 7),
child: Text(
"${this._productContent!.title}",
style: TextStyle(
color: Colors.black87, fontSize: ScreenAdapter.size(36)),
),
),
Container(
padding: EdgeInsets.only(top: 7),
child: Text(
"${this._productContent!.subTitle}",
style: TextStyle(
color: Colors.black54, fontSize: ScreenAdapter.size(28)),
),
),
Container(
padding: EdgeInsets.only(top: 7),
child: Row(
children: [
Expanded(
flex: 1,
child: Row(
children: [
Text("Price: "),
Text(
"${this._productContent!.price}",
style: TextStyle(
color: Colors.red,
fontSize: ScreenAdapter.size(46)),
)
],
),
),
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("Old Price: "),
Text(
"${this._productContent!.oldPrice}",
style: TextStyle(
color: Colors.black38,
fontSize: ScreenAdapter.size(32),
decoration: TextDecoration.lineThrough),
),
],
),
),
],
),
),
Container(
margin: EdgeInsets.only(top: 7),
height: ScreenAdapter.height(80.0),
child: InkWell(
onTap: () {
_attrBottomSheet();
},
child: Row(
children: [
Text(
"Select:",
style: TextStyle(fontWeight: FontWeight.bold),
),
Text("115, Black, XL, 1 pcs")
],
),
),
),
Divider(),
Container(
height: ScreenAdapter.height(80.0),
child: Row(
children: [
Text(
"Delivery Fees:",
style: TextStyle(fontWeight: FontWeight.bold),
),
Text("Free Delivery")
],
),
),
Divider(),
],
),
);
}
}
class ProductContentModel {
ProductContentItem? result;
ProductContentModel({this.result});
ProductContentModel.fromJson(Map<String, dynamic> json) {
result = json['result'] != null
? new ProductContentItem.fromJson(json['result'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.result != null) {
data['result'] = this.result!.toJson();
}
return data;
}
}
class ProductContentItem {
String? sId;
String? title;
String? cid;
Object? price;
String? oldPrice;
Object? isBest;
Object? isHot;
Object? isNew;
String? status;
String? pic;
String? content;
String? cname;
List<Attr>? attr;
String? subTitle;
Object? salecount;
ProductContentItem(
{this.sId,
this.title,
this.cid,
this.price,
this.oldPrice,
this.isBest,
this.isHot,
this.isNew,
this.status,
this.pic,
this.content,
this.cname,
this.attr,
this.subTitle,
this.salecount});
ProductContentItem.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
cid = json['cid'];
price = json['price'];
oldPrice = json['old_price'];
isBest = json['is_best'];
isHot = json['is_hot'];
isNew = json['is_new'];
status = json['status'];
pic = json['pic'];
content = json['content'];
cname = json['cname'];
if (json['attr'] != null) {
attr = <Attr>[];
json['attr'].forEach((v) {
attr!.add(new Attr.fromJson(v));
});
}
subTitle = json['sub_title'];
salecount = json['salecount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['cid'] = this.cid;
data['price'] = this.price;
data['old_price'] = this.oldPrice;
data['is_best'] = this.isBest;
data['is_hot'] = this.isHot;
data['is_new'] = this.isNew;
data['status'] = this.status;
data['pic'] = this.pic;
data['content'] = this.content;
data['cname'] = this.cname;
if (this.attr != null) {
data['attr'] = this.attr!.map((v) => v.toJson()).toList();
}
data['sub_title'] = this.subTitle;
data['salecount'] = this.salecount;
return data;
}
}
class Attr {
String? cate;
List<String>? list;
Attr({this.cate, this.list});
Attr.fromJson(Map<String, dynamic> json) {
cate = json['cate'];
list = json['list'].cast<String>();
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['cate'] = this.cate;
data['list'] = this.list;
return data;
}
}
When the exception was thrown, this was the stack:
\#0 \_ProductContentFirstState.\_getAttrWidget.\<anonymous closure (package:flutter_jdshop/pages/ProductContent/ProductContentFirst.dart:64:54)
Don't use ! without checking null. While the snippet is large, follow these steps.
Check null, and then perform operation the place you've used !. It will be like if(result!=null)result.add(new FocusItemModel.fromJson(v));
the children can be children: _getAttrItemWidget(attrItem)??[]
You aren't returning widgets from _getAttrItemWidget and others. It will be
List<Widget> _getAttrItemWidget(attrItem) { // no need to return null
List<Widget> attrItemList = [];
attrItem.list.forEach((item) {
....
print (item);
});
return attrItemList;
}
In short Don't use ! without checking null. or provide default value on null case.
Find more about null-safety.

ListView not showing any items due to null values

I have a model class ProductoModelo
import 'dart:convert';
List<ProductoModelo> productoModeloFromJson(String str) =>
List<ProductoModelo>.from(
json.decode(str).map((x) => ProductoModelo.fromJson(x)));
String productoModeloToJson(List<ProductoModelo> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class ProductoModelo {
ProductoModelo({
required this.id,
required this.sku,
required this.nombre,
required this.descripcion,
required this.destacado,
required this.visible_en_tienda,
required this.imagen,
required this.categoria,
required this.sub_cat,
required this.sub_cat2,
required this.sub_cat3,
});
String id;
String sku;
String nombre;
String descripcion;
String destacado;
String visible_en_tienda;
String imagen;
String categoria;
String sub_cat;
String? sub_cat2;
String? sub_cat3;
factory ProductoModelo.fromJson(Map<String, dynamic> json) => ProductoModelo(
id: json["id"],
sku: json["sku"],
nombre: json["nombre"],
descripcion: json["descripcion"],
destacado: json["destacado"],
visible_en_tienda: json["visible_en_tienda"],
imagen: json["imagen"],
categoria: json["categoria"],
sub_cat: json["sub_cat"],
sub_cat2: json["sub_cat2"] ?? "",
sub_cat3: json["sub_cat3"] ?? "");
Map<String, dynamic> toJson() => {
"id": id,
"sku": sku,
"nombre": nombre,
"descripcion": descripcion,
"destacado": destacado,
"visible_en_tienda": visible_en_tienda,
"imagen": imagen,
"categoria": categoria,
"sub_cat": sub_cat,
"sub_cat2": sub_cat2,
"sub_cat3": sub_cat3,
};
}
And this is how am I getting the JSON string from API
import 'package:capenergy_ns/modelos/producto_modelo.dart';
import 'package:http/http.dart' as http;
import '../constantes/constantes.dart';
import '../modelos/publicacion_modelo.dart';
Future<List<ProductoModelo>> fetchProductos() async {
String url = Constantes().URLProyecto+Constantes().APICarpeta+"get_productos.php";
final response = await http.get(Uri.parse(url));
print("RESPONSE BODY productos" +response.body.toString());
return productoModeloFromJson(response.body);
}
The problem is that the API is sending some null values for two of the fields: sub_cat2 and sub_cat3.
Here is an example of API output:
{"id":"120","sku":"853001","nombre":"Flu\u00eddo de rellenado de arrugas","destacado":"0","descripcion":"Flu\u00eddo de rellenado de arrugas. \u00c1cido hialur\u00f3nico. Formato Vial de f\u00e1cil aplicaci\u00f3n monouso. Caja de 10 unidades de 3 ml. [10 aplicaciones]","visible_en_tienda":"1","precio":"47.8","imagen":"fluidoAcidoroll-on.jpg","categoria":"Cremas","sub_cat":"Fluidos","sub_cat2":"Linea Medicina est\u00e9tica","sub_cat3":null}
Due to these null values I am not getting any objects to a ListView.
How can I solve this issue.
EDIT
Here you have the class where I am getting and showing the API items
Expanded(
child: Container(
child: FutureBuilder(
future: fetchProductos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? filteredList = snapshot.data as List;
print("a minusculas:" +
_controller.text.toLowerCase());
filteredList = filteredList
.where((element) => (element.nombre
.toLowerCase()
.contains(
_controller.text.toLowerCase()) ||
element.nombre.toLowerCase().contains(
_controller.text.toLowerCase())))
.toList();
print("texto filtrado=" +
_controller.text.toLowerCase());
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ListView.builder(
itemCount: filteredList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, index) {
ProductoModelo producto = filteredList![index];
return new GestureDetector(
onTap: () {
},
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: new Card(
elevation: 6,
child: Column(
children: [
new Container(
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Container(
height: 80,
width: 80,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(Constantes().URLProductos+producto.imagen)
)
),
),
SizedBox(width: 5,),
Column(
children: [
//nombre
Container(
width: MediaQuery.of(context).size.width -120,
height: 60,
child: Padding(
padding:
const EdgeInsets
.all(1.0),
child: Text(
"${producto.nombre}",
maxLines: 2,
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight
.bold),
),
),
),
//categoria
Container(
width: MediaQuery.of(context).size.width -120,
height: 60,
child: Padding(
padding:
const EdgeInsets
.all(1.0),
child: Text(
"${producto.categoria}",
style: TextStyle(
fontSize: 14,
),
),
),
),
],
),
],
),
],
),
],
),
],
),
),
],
),
),
),
);
},
),
);
}
First change your builder to this:
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
....
}else (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}else {
return SizedBox();
}
then change your fetchProductos to this:
Future<List<ProductoModelo>> fetchProductos() async {
try {
String url = Constantes().URLProyecto +
Constantes().APICarpeta +
"get_productos.php";
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
print("RESPONSE BODY productos" + response.body.toString());
return productoModeloFromJson(response.body);
}else {
return [];
}
} catch (e) {
print("error = $e");
return [];
}
}
also change your model to this:
factory ProductoModelo.fromJson(Map<String, dynamic> json) => ProductoModelo(
id: json["id"] ?? "",
sku: json["sku"] ?? "",
nombre: json["nombre"] ?? "",
descripcion: json["descripcion"] ?? "",
destacado: json["destacado"] ?? "",
visible_en_tienda: json["visible_en_tienda"] ?? "",
imagen: json["imagen"] ?? "",
categoria: json["categoria"] ?? "",
sub_cat: json["sub_cat"] ?? "",
sub_cat2: json["sub_cat2"] ?? "",
sub_cat3: json["sub_cat3"] ?? "");

How to fetch data from REST API and display on Listview, Flutter

I am fairly new to using REST API to GET data and display in a Listview in Flutter. I have since been working on something like this, But i get Lost along the line. Hence I need Help with this.
My code Goes thus
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class TransactionDetails {
final String avatar;
final String name;
final String date;
final String amount;
TransactionDetails({required this.avatar, required this.name, required this.date, required this.amount});
factory TransactionDetails.fromJson(Map<String, dynamic> json) {
return TransactionDetails(
avatar: json['avatar'],
name: json['name'],
date: json['date'],
amount: json['amount']);
}
}
class BaseScreen extends StatelessWidget {
const BaseScreen({Key? key}) : super(key: key);
Future<TransactionDetails> fetchTransaction() async {
final response = await http
.get('https://brotherlike-navies.000webhostapp.com/people/people.php');
if (response.statusCode == 200) {
return TransactionDetails.fromJson(json.decode(response.body));
} else {
throw Exception('Request Failed.');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: double.infinity,
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Container(
margin: const EdgeInsets.all(15),
width: 319,
height: 100,
color: Colors.green,
alignment: Alignment.center,
child: const Text(
'\$5200.00',
style: TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
Container(
margin: const EdgeInsets.all(15),
width: 319,
height: 100,
color: Colors.green,
alignment: Alignment.center,
child: const Text(
'\$1200.00',
style: TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
SizedBox(height: 24),
],
),
),
Padding(
padding: EdgeInsets.all(15),
child: Text(
"Recent Transactions",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold, color: Colors.green),
),
),
ListView() //Display the data here from the REST API
],
)));
}
}
How do I Display it on the Listview? Please I need help with this. Just for the sake of clarification as I am learning here with this.
Try below code:
Your TransactionDetails Class
class TransactionDetails {
String? avatar;
String? name;
String? date;
String? amount;
TransactionDetails({
this.avatar,
this.name,
this.date,
this.amount,
});
TransactionDetails.fromJson(Map<String, dynamic> json) {
avatar = json['avatar'];
name = json['name'];
date = json['date'];
amount = json['amount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['avatar'] = avatar;
data['name'] = name;
data['date'] = date;
data['amount'] = amount;
return data;
}
}
API Call:
Future<List<TransactionDetails>> fetchAlbum() async {
final response = await http.get(Uri.parse(
'https://brotherlike-navies.000webhostapp.com/people/people.php'));
if (response.statusCode == 200) {
final List result = json.decode(response.body);
return result.map((e) => TransactionDetails.fromJson(e)).toList();
} else {
throw Exception('Failed to load data');
}
}
Your Widget:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: double.infinity,
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Container(
margin: const EdgeInsets.all(15),
width: 319,
height: 100,
color: Colors.green,
alignment: Alignment.center,
child: const Text(
'\$5200.00',
style: TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
Container(
margin: const EdgeInsets.all(15),
width: 319,
height: 100,
color: Colors.green,
alignment: Alignment.center,
child: const Text(
'\$1200.00',
style: TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 24),
],
),
),
const Padding(
padding: EdgeInsets.all(15),
child: Text(
"Recent Transactions",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.green),
),
),
Center(
child: FutureBuilder<List<TransactionDetails>>(
future: fetchAlbum(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Image.network(
snapshot.data![index].avatar.toString()),
),
title: Text(snapshot.data![index].name.toString()),
trailing:
Text(snapshot.data![index].amount.toString()),
subtitle: Text(snapshot.data![index].date.toString()),
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
),
),
],
),
Result Screen->
There are multiple ways you can achieve this.
Using FutureBuilder
Using StatefulWidget & setState
Currently, you are using a StatelessWidget (BaseScreen) so let's just go with FutureBuilder.
On hitting the url that you have given:
https://brotherlike-navies.000webhostapp.com/people/people.php
It gives the following response:
[{"avatar":"https:\/\/static.vecteezy.com\/system\/resources\/thumbnails\/002\/002\/403\/small\/man-with-beard-avatar-character-isolated-icon-free-vector.jpg","name":"Kayo Johnson","date":"09\/29\/2022","amount":"5000.00"},{"avatar":"https:\/\/static.vecteezy.com\/system\/resources\/thumbnails\/002\/002\/403\/small\/man-with-beard-avatar-character-isolated-icon-free-vector.jpg","name":"Kuta Joy","date":"09\/29\/2022","amount":"5000.00"},{"avatar":"https:\/\/static.vecteezy.com\/system\/resources\/thumbnails\/001\/993\/889\/small\/beautiful-latin-woman-avatar-character-icon-free-vector.jpg","name":"Timmi Phillips","date":"09\/28\/2022","amount":"3500.00"}]
It returns a list of transaction details objects. Hence, you should add another method in your model TransactionDetail which will return a list of TransactionDetails as follows:
class TransactionDetails {
final String avatar;
final String name;
final String date;
final String amount;
TransactionDetails(
{required this.avatar,
required this.name,
required this.date,
required this.amount});
factory TransactionDetails.fromJson(Map<String, dynamic> json) {
return TransactionDetails(
avatar: json['avatar'],
name: json['name'],
date: json['date'],
amount: json['amount']);
}
static List<TransactionDetails> fromJsonList(dynamic jsonList) {
final transactionDetailsList = <TransactionDetails>[];
if (jsonList == null) return transactionDetailsList;
if (jsonList is List<dynamic>) {
for (final json in jsonList) {
transactionDetailsList.add(
TransactionDetails.fromJson(json),
);
}
}
return transactionDetailsList;
}
}
Update your fetchTransaction method as follows:
Future<List<TransactionDetails>> fetchTransaction() async {
final response = await http
.get('https://brotherlike-navies.000webhostapp.com/people/people.php');
if (response.statusCode == 200) {
return TransactionDetails.fromJsonList(json.decode(response.body));
} else {
throw Exception('Request Failed.');
}
}
Just wrap your ListView widget with a FutureBuilder widget as follows:
FutureBuilder(
future: fetchTransaction(),
builder: (context, AsyncSnapshot<List<TransactionDetails>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const Center(child: Text('Something went wrong'));
}
return ListView.builder(
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index].name),
subtitle: Text(snapshot.data![index].amount),
);
});
}
return const CircularProgressIndicator();
},
),