I'm using FutureBuilder for fetching API request. but it only builds once and it doesn't update ui unless hot reloded. i aslo tried StreamBuilder and convert the future method to stream. so how can i update my ui. please i already asked this question and got no answer. whay is this happening?
model
import 'dart:convert';
List<User> usersFromJson(String str) =>
List<User>.from(json.decode(str).map((json) => User.fromJson(json)));
String usersToJson(List<User> data) =>
json.encode(List<dynamic>.from(data.map((e) => e.toJson())));
class User {
int? id;
String? name;
String? username;
String? email;
String? role;
User({this.id, this.name, this.username, this.email, this.role});
#override
toString() => 'User: $name';
factory User.fromJson(Map<String, dynamic> json) => User(
email: json['email'],
name: json['name'],
id: json['id'],
username: json['username'],
role: json['role']);
Map<String, dynamic> toJson() =>
{"name": name, "username": username, "email": email, "role": role};
}
api call
Future fetchUsers() async {
Uri url = Uri.parse("${BASE_URL}user");
final response = await http.get(url, headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
});
var userview = <User>[];
if (response.statusCode == 200) {
var jsonres = json.decode(response.body);
for (var res in jsonres) {
userview.add(User.fromJson(res));
}
}
return userview;
}
Future createUser(String name, String username, String email, String password,
String roles) async {
Uri url = Uri.parse("${BASE_URL}user");
final response = await http
.post(url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
},
body: jsonEncode(<String, dynamic>{
'name': name,
'email': email,
'username': username,
'password': password,
'roles': roles
}))
.then((value) => fetchUsers());
}
and my page
StreamBuilder(
stream: fetchUsers().asStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: [
PaginatedDataTable(
sortColumnIndex: sortColumnIndex,
sortAscending: isAscending,
columns: [
DataColumn(
label: const Text("Id"),
onSort: onSort),
DataColumn(
label: const Text("Name"),
onSort: onSort),
DataColumn(
label: const Text("Username"),
onSort: onSort),
DataColumn(
label: const Text("Email"),
onSort: onSort),
DataColumn(
label: const Text("Roles"),
onSort: onSort),
DataColumn(
label: const Text("Actions"),
onSort: onSort),
],
header: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Manage Users",
style: TextStyle(
fontSize: width * 0.04,
fontWeight: FontWeight.normal),
),
MaterialButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: height * 0.9,
child:
BottomSheetWidget(),
),
shape:
const RoundedRectangleBorder(
borderRadius:
BorderRadius.only(
topLeft: Radius
.circular(
15),
topRight: Radius
.circular(
15))));
},
color: const Color.fromRGBO(
30, 119, 66, 1),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10)),
child: Text(
"Add User",
style: TextStyle(
color: Colors.white,
fontSize: width * 0.03),
),
)
],
),
source: dataSource(
snapshot.data! as List<User>))
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return const Center(
child: CircularProgressIndicator());
})
additionally i'm using paginated datatable and this is the code for that.(it's related)
in the page
DataTableSource dataSource(List<User> userList) =>
MyTable(datasList: userList, context: context);
and the datasource page i'm calling the createUser here.
class MyTable extends DataTableSource {
MyTable({required this.datasList, required this.context});
final List<User> datasList;
BuildContext context;
Widget Button(String title, Color color, String id) {
return MaterialButton(
onPressed: () {
//deleteUser(id);
//updateUser(id, title);
createUser("name2", "user2", "email2#email.com", "password", "Admin");
},
child: Text(
title,
style: const TextStyle(color: Colors.white),
),
color: color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
);
}
#override
DataRow? getRow(int index) {
return DataRow.byIndex(index: index, cells: [
DataCell(Text(datasList[index].id.toString())),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].name.toString(),
overflow: TextOverflow.ellipsis,
),
)),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].username.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].email.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].role.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(Row(
children: [
Button("Edit", Colors.lightBlue, datasList[index].id.toString()),
const SizedBox(
width: 5,
),
Button("Delete", Colors.red, datasList[index].id.toString()),
],
)),
]);
}
#override
bool get isRowCountApproximate => false;
#override
int get rowCount => datasList.length;
#override
int get selectedRowCount => 0;
}
StreamBuilder class
Widget rebuilding is scheduled by each interaction, using
State.setState, but is otherwise decoupled from the timing of the
stream.
i see the code and can't found any issue. but im bit doubt with asStream().
as documentation said: asStream method:
Creates a Stream containing the result of this future.
maybe you can try with this : stream.fromFuture
found the issue here: https://stackoverflow.com/a/55169382/12838877
since your fetch method is not common , it is a Future method that return value of Future value.
Stream.fromFuture(fetchUsers)
it's just my doubt. hope solve the problem. because it's too long to explain in the comments, so I'll put it in the answer section
Related
So basically it is a comment box that comes with or without an image. So the img url is nullable. I am trying to deserialize it.
Here is the Comment Model.
// To parse this JSON data, do
//
// final productComment = productCommentFromJson(jsonString);
import 'package:meta/meta.dart';
import 'dart:convert';
ProductComment productCommentFromJson(String str) =>
ProductComment.fromJson(json.decode(str));
String productCommentToJson(ProductComment data) => json.encode(data.toJson());
class ProductComment {
const ProductComment({
this.id,
this.comment,
this.owner,
this.productId,
this.imgsUrl,
this.imgsName,
this.createdAt,
});
final int? id;
final String? comment;
final String? owner;
final int? productId;
final List<String>? imgsUrl;
final List<String>? imgsName;
final DateTime? createdAt;
factory ProductComment.fromJson(Map<String, dynamic> json) => ProductComment(
id: json["id"],
comment: json["comment"],
owner: json["owner"],
productId: json["product_id"],
// imgsUrl: List<String>.from(json["imgs_url"].map((x) => x)),
imgsUrl: json["imgs_url"] == null
? null
: List<String>.from(json["imgs_url"].map((x) => x)),
imgsName: List<String>.from(json["imgs_name"].map((x) => x)),
createdAt: DateTime.parse(json["created_at"]),
);
Map<String, dynamic> toJson() => {
"id": id,
"comment": comment,
"owner": owner,
"product_id": productId,
"imgs_name": List<dynamic>.from(imgsName!.map((x) => x)),
// "imgs_url": List<dynamic>.from(imgsUrl!.map((x) => x)),
"imgs_url":
imgsUrl == null ? null : List<dynamic>.from(imgsUrl!.map((x) => x)),
"created_at": createdAt!.toIso8601String(),
};
}
I am trying to decode the imgs_url wether it has an image or not.
Wrap(
children: <Widget>[
Card(
margin: const EdgeInsets.all(8),
child: Container(
decoration: const BoxDecoration(
color: Color.fromARGB(255, 221, 217, 217),
borderRadius: BorderRadius.all(Radius.circular(10))),
padding:
const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Column(
children: <Widget>[
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: Isize().borderRadius),
child: Text(
widget.comment,
overflow: flag ? TextOverflow.ellipsis : null,
style: const TextStyle(
fontSize: 15,
),
),
),
InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
flag ? "show more" : "show less",
style: const TextStyle(
color: lilac, fontFamily: 'FiraCode'),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
Builder(builder: (context) {
if (widget.image!.isNotEmpty) {
return Container();
}
return SizedBox(
height: 100,
child: GridView.count(
mainAxisSpacing: 1,
crossAxisSpacing: 1,
crossAxisCount: 6,
children:
List.generate(widget.image!.length, (index) {
return Center(
child: CachedNetworkImage(
memCacheHeight: 20,
memCacheWidth: 20,
imageUrl: widget.image![index]!,
imageBuilder: (context, imageProvider) =>
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: Isize().borderRadius,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
colorFilter: const ColorFilter.mode(
Colors.black45,
BlendMode.darken,
),
),
),
),
placeholder: (context, url) => Center(
child: Image.asset(IhIcon().ihemyilogo),
),
errorWidget: (context, url, error) =>
const Icon(
Icons.error,
color: Colors.deepOrangeAccent,
),
),
);
}),
));
})
],
)),
),
],
);
For the repository which also has a map:
Future<List<ProductComment>> productCommentsByID(int productID) async {
final uri = Uri.parse('${ep.mainUrl}/api/v1/products/comments/$productID');
debugPrint('Comments by Product ID = $uri');
final response = await http.get(uri);
if (response.statusCode == 200) {
if (response.body.isNotEmpty) {
final comments = List<ProductComment>.from(
json.decode(response.body).map(
(data) => ProductComment.fromJson(data),
),
);
debugPrint('Fucker says hello ${comments.toString()}');
return comments;
} else {
throw ErrorEmptyResponse();
}
} else {
throw ErrorGettingGames(
'Error getting Comments With Product ID $productID');
}
}
Please i need help.I cant understand which of the maps that has the issue. I just get this error:
I/flutter ( 3603): Transition { currentState: CommentState([], Instance of 'ProductComment', 0, CommentStatus.error), event: CommentsByProductID(1), nextState: CommentError(NoSuchMethodError: The method 'map' was called on null.
I/flutter ( 3603): Receiver: null
I think your imgsName may be null too, try change this:
imgsName: List<String>.from(json["imgs_name"].map((x) => x)),
to this:
imgsName: json["imgs_name"] == null? null: List<String>.from(json["imgs_name"].map((x) => x)),
I was updating UI of my Data table by assigning fetch result of an api and assigning it to Getx List. this works fine but when trying to delete, create and update data the ui is not responding. according to my code i was calling the get controller and assigning the value is by using initState() . so this only call it once initially. when i try to delete the whole page will reload even the widgets outside the data table. but those widgets display the data from the same api result. I'll post my code now.
user model
import 'role_model.dart';
import 'dart:convert';
List<User> usersFromJsonFromModel(String str) =>
List<User>.from(json.decode(str).map((json) => User.fromJson(json)));
String usersToJsonFromModel(List<User> data) =>
json.encode(List<dynamic>.from(data.map((e) => e.toJson())));
class User {
int? id;
String? name;
String? username;
String? email;
String? emailVerifiedAt;
String? twoFactorConfirmedAt;
String? currentTeamId;
String? profilePhotoPath;
String? warehouseId;
String? createdAt;
String? updatedAt;
String? profilePhotoUrl;
List<Roles>? roles;
User(
{this.id,
this.name,
this.username,
this.email,
this.emailVerifiedAt,
this.twoFactorConfirmedAt,
this.currentTeamId,
this.profilePhotoPath,
this.warehouseId,
this.createdAt,
this.updatedAt,
this.profilePhotoUrl,
this.roles});
User.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
username = json['username'];
email = json['email'];
emailVerifiedAt = json['email_verified_at'];
twoFactorConfirmedAt = json['two_factor_confirmed_at'];
currentTeamId = json['current_team_id'];
profilePhotoPath = json['profile_photo_path'];
warehouseId = json['warehouse_id'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
profilePhotoUrl = json['profile_photo_url'];
if (json['roles'] != null) {
roles = <Roles>[];
json['roles'].forEach((v) {
roles!.add(Roles.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['id'] = id;
data['name'] = name;
data['username'] = username;
data['email'] = email;
data['email_verified_at'] = emailVerifiedAt;
data['two_factor_confirmed_at'] = twoFactorConfirmedAt;
data['current_team_id'] = currentTeamId;
data['profile_photo_path'] = profilePhotoPath;
data['warehouse_id'] = warehouseId;
data['created_at'] = createdAt;
data['updated_at'] = updatedAt;
data['profile_photo_url'] = profilePhotoUrl;
if (roles != null) {
data['roles'] = roles!.map((v) => v.toJson()).toList();
}
return data;
}
}
API call
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:store_mgt_project/controller/user_controller.dart';
import 'package:store_mgt_project/utils/const.dart';
import '../users_model.dart';
Future<List<User>> fetchUsers() async {
Uri url = Uri.parse("${BASE_URL}user");
final response = await http.get(url, headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
});
var userview = <User>[];
if (response.statusCode == 200) {
var jsonres = json.decode(response.body);
for (var res in jsonres) {
userview.add(User.fromJson(res));
}
}
return userview;
}
Future createUser(String name, String username, String email, String password,
String roles) async {
final UserController userController = Get.put(UserController());
Uri url = Uri.parse("${BASE_URL}user");
var userview = <User>[];
var jsonres;
final response = await http
.post(url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
},
body: jsonEncode(<String, dynamic>{
'name': name,
'email': email,
'username': username,
'password': password,
'roles': roles.toString()
}))
.then((value) => {
if (value.statusCode == 200)
{
userController.setuserPress(true),
Get.back(),
userController.setuserPress(false)
}
else if (value.statusCode == 422)
{
Get.snackbar("Cannot create $name",
"username or email is already been taken",
duration: const Duration(seconds: 2),
colorText: Colors.black,
backgroundColor: Colors.white),
}
else
{
throw Get.snackbar("failed", "failed to create user $name",
duration: const Duration(seconds: 2),
colorText: Colors.black,
backgroundColor: Colors.white),
}
});
}
Future updateUser(var id, String name, String username, String email,
String password, String roles) async {
final UserController userController = Get.put(UserController());
Uri url = Uri.parse("${BASE_URL}user/$id");
final response = await http
.patch(
url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
},
body: jsonEncode(<String, String>{
'name': name,
'email': email,
'username': username,
// 'password': password,
'roles': roles
}),
)
.then((value) => {
if (value.statusCode == 200)
{
userController.setuserPress(true),
Get.back(),
userController.setuserPress(false)
}
else
{
throw Get.snackbar("failed", "failed to update user $name",
duration: const Duration(seconds: 2),
colorText: Colors.black,
backgroundColor: Colors.white),
}
});
}
Future deleteUser(var id) async {
final UserController userController = Get.put(UserController());
Uri url = Uri.parse("${BASE_URL}user/$id");
final response = await http.delete(url, headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer $TOKEN',
}).then((value) => {
userController.setuserPress(true),
//usersToJsonFromModel(jsonDecode(value.body)),
userController.setuserDelete(jsonDecode(value.body)),
Get.snackbar(
"Deleted $id", "You've deleted id of ${jsonDecode(value.body)}",
duration: const Duration(seconds: 30),
colorText: Colors.black,
backgroundColor: Colors.white),
});
}
userController page
import 'package:get/get.dart';
import 'package:store_mgt_project/models/api/user_api.dart';
import 'package:store_mgt_project/models/users_model.dart';
class UserController extends GetxController {
final _userPress = false.obs;
get userPress => _userPress.value;
set userPress(value) => _userPress.value = value;
List<User> _user = <User>[].obs;
List<User> _userDisplay = <User>[].obs;
get user => _user;
set user(value) => _user = value;
get userDisplay => _userDisplay;
set userDisplay(value) => _userDisplay = value;
setuserPress(bool value) {
userPress = value;
update();
}
setuser(List<User> value) {
user = value;
update();
}
setuserDelete(User value) {
_user.remove(value);
_user.join();
refresh();
}
setuserDisplay(List<User> value) {
userDisplay = value;
update();
}
}
finally homepage
DataTableSource dataSource(List<User> userList) =>
MyTable(datasList: userList, context: context);
final UserController userController = Get.put(UserController());
#override
void initState() {
// TODO: implement initState
super.initState();
setState(() {
fetchUsers().then((value) => {
setState(() {
userController.setuser(value);
userController.setuserDisplay(userController.user);
})
});
});
}
...
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: width * 0.02),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
return userController.userPress
? const Center(child: CircularProgressIndicator())
: StreamBuilder(
stream: fetchUsers().asStream(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
height: height * 0.15,
width: width / 1.5,
decoration: BoxDecoration(
color: const Color.fromRGBO(30, 119, 66, 1),
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: width * 0.015),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${snapshot.data.length}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: height * 0.05),
),
SizedBox(
height: height * 0.008,
),
Text(
"Total Users",
style: TextStyle(
color: Colors.white,
fontSize: height * 0.02,
fontWeight: FontWeight.w600),
)
],
),
),
);
} else {
return CircularProgressIndicator();
}
});
}),
SizedBox(
height: height * 0.02,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.grey)),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: width * 0.01),
child: Obx(() {
return userController.userPress
? const Center(child: CircularProgressIndicator())
: PaginatedDataTable(
sortColumnIndex: sortColumnIndex,
sortAscending: isAscending,
columns: const [
DataColumn(
label: Text("Id"),
),
DataColumn(label: Text("Name")),
DataColumn(
label: Text("Username"),
),
DataColumn(label: Text("Email")),
DataColumn(label: Text("Roles")),
DataColumn(label: Text("Actions")),
],
header: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: width * 0.6,
child: TextField(
decoration:
InputDecoration(hintText: "search"),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
userController.userDisplay =
userController.user
.where((element) {
var _name =
element.name!.toLowerCase();
var _username =
element.username!.toLowerCase();
return _name.contains(text) ||
_username.contains(text);
}).toList();
});
},
),
),
MaterialButton(
onPressed: () {
showMaterialModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: height * 0.9,
child: BottomSheetWidget(),
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight:
Radius.circular(15))));
},
color: const Color.fromRGBO(30, 119, 66, 1),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10)),
child: Text(
"Add User",
style: TextStyle(
color: Colors.white,
fontSize: width * 0.03),
),
)
],
),
source: dataSource(userController.userDisplay));
})),
)
],
),
));
...
class MyTable extends DataTableSource {
MyTable({required this.datasList, required this.context});
final List<User> datasList;
BuildContext context;
final UserController userController = Get.put(UserController());
Widget Button(String title, Color color, String id, bool delete, String? name,
String? email, String? username, String? role) {
return MaterialButton(
onPressed: () async {
final height = MediaQuery.of(context).size.height;
delete
? await deleteUser(id)
: await showMaterialModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: height * 0.9,
child: BottomSheetEditWidget(
name: name,
email: email,
username: username,
role: role,
id: id,
),
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))));
userController.setuserPress(false);
},
child: Text(
title,
style: const TextStyle(color: Colors.white),
),
color: color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
);
}
#override
DataRow? getRow(int index) {
return DataRow.byIndex(index: index, cells: [
DataCell(Text("${index + 1}".toString())),
DataCell(
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].name.toString(),
overflow: TextOverflow.ellipsis,
),
),
),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].username.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].email.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 100),
child: Text(
datasList[index].roles![0].name.toString(),
overflow: TextOverflow.ellipsis,
))),
DataCell(Row(
children: [
Button(
"Edit",
Colors.lightBlue,
datasList[index].id.toString(),
false,
datasList[index].name,
datasList[index].email,
datasList[index].username,
datasList[index].roles![0].name),
const SizedBox(
width: 5,
),
Button(
"Delete",
Colors.red,
datasList[index].id.toString(),
true,
datasList[index].name,
datasList[index].email,
datasList[index].username,
datasList[index].roles![0].name),
],
)),
]);
}
#override
bool get isRowCountApproximate => false;
#override
int get rowCount => datasList.length;
#override
int get selectedRowCount => 0;
}
so how can i use crud operation and update ui at the same time?
[update of the new approach]
Obx(() {
return userController.userPress
? const Center(child: CircularProgressIndicator())
: StreamBuilder(
stream: fetchUsers().asStream(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
height: height * 0.15,
width: width / 1.5,
decoration: BoxDecoration(
color: const Color.fromRGBO(30, 119, 66, 1),
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: width * 0.015),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${snapshot.data.length}",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: height * 0.05),
),
SizedBox(
height: height * 0.008,
),
Text(
"Total Users",
style: TextStyle(
color: Colors.white,
fontSize: height * 0.02,
fontWeight: FontWeight.w600),
)
],
),
),
);
} else {
return CircularProgressIndicator();
}
});
}),
You can use Obx for this.
For this example I'll use the UserPress variable to explain to you. And you create an appropriate variable to do the action...
In your Ui you only needed put an obx in place that you want update:
Obx(() =>
(userController.userPress)
?
return MaterialButton(
onPressed: () async {
final height = MediaQuery.of(context).size.height;
delete
? await deleteUser(id)
: await showMaterialModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: height * 0.9,
child: BottomSheetEditWidget(
name: name,
email: email,
username: username,
role: role,
id: id,
),
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))));
userController.setuserPress(false);
}
: return MaterialButton(
onPressed: null,
),
),
In this example, if userPress = true the button can be clicked.
If userPress = false, the button don't be clicked.
I am trying to select an index from a list of images in flutter. While passing the image list to the Image.network() i get an error:
The following NetworkImageLoadException was thrown resolving an image codec:
HTTP request failed, statusCode: 500, http://10.0.2.2:8000/static/product_images/b0bf3474f9c5a6b6cfcf.jpg,http://10.0.2.2:8000/static/product_images/e307dccf6bbc2700e683.jpg,http://10.0.2.2:8000/static/product_images/6e91cff1ce07d72fc127.jpg
Please how do i seperate the images so that only the index i pick is selected?
Below is the Product Model:
import 'dart:convert';
List<ProductModel> productsFromJson(String str) => List<ProductModel>.from(
json.decode(str).map((x) => ProductModel.fromJson(x)));
String productsToJson(List<ProductModel> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class ProductModel {
ProductModel({
required this.name,
required this.price,
required this.isActive,
required this.imgsUrl,
required this.id,
required this.description,
required this.ownerId,
});
String name;
double price;
bool isActive;
List<String> imgsUrl;
int id;
String description;
int ownerId;
factory ProductModel.fromJson(Map<String, dynamic> json) => ProductModel(
name: json["name"],
price: json["price"].toDouble(),
isActive: json["is_active"],
imgsUrl: List<String>.from(json["imgs_url"].map((x) => x)),
id: json["id"],
description: json["description"],
ownerId: json["owner_id"],
);
Map<String, dynamic> toJson() => {
"name": name,
"price": price,
"is_active": isActive,
"imgs_url": List<dynamic>.from(imgsUrl.map((x) => x)),
"id": id,
"description": description,
"owner_id": ownerId,
};
}
I am using a futurebuilder with dio to fetch the data from the API:
FutureBuilder<List<ProductModel>>(
future: productModel,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('${snapshot.error}');
} else if (snapshot.hasData) {
debugPrint('${snapshot.data}');
return SizedBox(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Container(
decoration: const BoxDecoration(
color: styles.AppColors.facebookBlue),
child: Text('$finalName products online'),
),
),
Expanded(
child: StaggeredGridView.countBuilder(
crossAxisCount: 2,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(children: [
Container(
height: 180,
width: double.infinity,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(4)),
child:
Image.network(snapshot
.data![index].imgsUrl[0])
]),
const SizedBox(
height: 8,
),
Text(snapshot.data![index].name),
Text(
"\$${snapshot.data![index].price.toString()}",
)
],
),
),
);
},
staggeredTileBuilder: (int index) =>
const StaggeredTile.fit(1),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
),
],
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
For the API call with DioClient:
Future<List<ProductModel>> fetchVProduct() async {
await SecureStorage.readSecureData("vt")
.then((value) => finalToken = value);
try {
final response = await Dio().get(
'$productsEndpoint/me/items/',
options: Options(
headers: {
'Content-Type':
'application/x-www-form-urlencoded;charset=UTF-8;application/json;multipart/form-data',
'Accept': 'application/json',
"Authorization": "Bearer $finalToken",
},
followRedirects: false,
validateStatus: (status) {
return status! < 500;
}),
);
debugPrint(response.toString());
return (response.data as List)
.map((x) => ProductModel.fromJson(x))
.toList();
} on DioError catch (e) {
debugPrint("Status code: ${e.response?.statusCode.toString()}");
throw Exception("Failed to load currently Logged in Vendor Products");
}
}
For the sample json from my apicall:
{
"name": "sa",
"price": 44,
"is_active": true,
"imgs_url": [
"http://10.0.2.2:8000/static/product_images/f3e6421737643e583a1d.jpg, http://10.0.2.2:8000/static/product_images/b53bf8aeb27d27a739cc.jpg, http://10.0.2.2:8000/static/product_images/75a80e7c04ebaed3e425.jpg"
],
"id": 43,
"description": "hfg",
"owner_id": 1
}
Please what do i do next? Thank you.
In data imgs_url is a single string containing three urls.
"imgs_url": [
"http://10.0.2.2:8000/static/product_images/f3e6421737643e583a1d.jpg, http://10.0.2.2:8000/static/product_images/b53bf8aeb27d27a739cc.jpg, http://10.0.2.2:8000/static/product_images/75a80e7c04ebaed3e425.jpg"
],
Change it to
"imgs_url": [
"http://10.0.2.2:8000/static/product_images/f3e6421737643e583a1d.jpg",
"http://10.0.2.2:8000/static/product_images/b53bf8aeb27d27a739cc.jpg",
"http://10.0.2.2:8000/static/product_images/75a80e7c04ebaed3e425.jpg"
],
I'm making a POST request using HTTPClient and posting a form's data. The request is successful when I tried with only 3 fields, but now I want to add all the fields so I added the complete JSON object model in the parameter and body of the POST function.
I've used Postman to get the entire JSON data and used quicktype.io tool to parse it into a model class which I've passed as parameters to the function and also to the requests body.
I have a save button in the UI which when clicked should post all the form data, I'm having trouble building that function saveContact() and getting the fields from the Model and initializing it to the form field controllers.
API_Manager class:
Future<AddContactModel> addContact(AddContactModel contact) async {
var client = http.Client();
String addContactUrl =
"https://example.com/ma/api/contacts/new";
String basicAuth = 'Basic examplebasicauthkey';
var response = await client.post(addContactUrl,
headers: <String, String>{
'authorization': basicAuth,
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
body: contact.toJson()); //from the Model class
// print(response.statusCode);
developer.log(response.body);
if (response.statusCode == 201) {
final String responseString = response.body;
return addContactModelFromJson(responseString);
} else {
return null;
}
}
SAVE FUNCTION to call POST request's function:
Future saveContact() async { //Need to call function here, get Model fields
//and initialize it to the controllers.
//await API_Manager().addContact(contact);
//Something like this but sure of the full code
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Scaffold(
appBar: AppBar(
title: Text('Add Contact'),
actions: <Widget>[
FlatButton(
textColor: Colors.white,
onPressed: () async {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState.validate()) {
await saveContact();
}
},
child: Text(
'SAVE',
style: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
shape:
CircleBorder(side: BorderSide(color: Colors.transparent)),
)
],
),
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.all(5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_contact == null
? Container()
:
//Text("The user ${_contact.contact.fields.all.firstname} is created successfully at time ${_contact.contact.lastActive.toIso8601String()}"),
TextFormField(
onSaved: null,
controller: _ipCountryCode,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'IP Country Code',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.all(8)),
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: DateTimeFormField(
decoration: InputDecoration(
labelText: 'Time First Seen',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.all(8)),
onDateSelected: (DateTime value) {
setState(() {
timeFirstSeen = value;
});
},
),
),
],
),
TextFormField(
onSaved: null,
controller: _eventRevenue,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'Event Revenue',
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.all(8)),
),
//Not the full code
addContactModel class:
import 'dart:convert';
AddContactModel addContactModelFromJson(String str) => AddContactModel.fromJson(json.decode(str));
String addContactModelToJson(AddContactModel data) => json.encode(data.toJson());
class AddContactModel {
AddContactModel({
this.contact,
});
Contact contact;
factory AddContactModel.fromJson(Map<String, dynamic> json) => AddContactModel(
contact: Contact.fromJson(json["contact"]),
);
Map<String, dynamic> toJson() => {
"contact": contact.toJson(),
};
}
class Contact {
Contact({
this.isPublished,
this.dateAdded,
this.dateModified,
this.createdBy,
this.createdByUser,
this.modifiedBy,
this.modifiedByUser,
this.id,
this.points,
this.color,
this.fields,
this.lastActive,
this.owner,
this.ipAddresses,
this.tags,
this.utmtags,
this.stage,
this.dateIdentified,
this.preferredProfileImage,
this.doNotContact,
this.frequencyRules,
});
bool isPublished;
DateTime dateAdded;
dynamic dateModified;
int createdBy;
String createdByUser;
dynamic modifiedBy;
dynamic modifiedByUser;
int id;
int points;
dynamic color;
Fields fields;
dynamic lastActive;
dynamic owner;
List<dynamic> ipAddresses;
List<dynamic> tags;
dynamic utmtags;
dynamic stage;
dynamic dateIdentified;
dynamic preferredProfileImage;
List<dynamic> doNotContact;
List<dynamic> frequencyRules;
factory Contact.fromJson(Map<String, dynamic> json) => Contact(
isPublished: json["isPublished"],
dateAdded: DateTime.parse(json["dateAdded"]),
dateModified: json["dateModified"],
createdBy: json["createdBy"],
createdByUser: json["createdByUser"],
modifiedBy: json["modifiedBy"],
modifiedByUser: json["modifiedByUser"],
id: json["id"],
points: json["points"],
color: json["color"],
fields: Fields.fromJson(json["fields"]),
lastActive: json["lastActive"],
owner: json["owner"],
ipAddresses: List<dynamic>.from(json["ipAddresses"].map((x) => x)),
tags: List<dynamic>.from(json["tags"].map((x) => x)),
utmtags: json["utmtags"],
stage: json["stage"],
dateIdentified: json["dateIdentified"],
preferredProfileImage: json["preferredProfileImage"],
doNotContact: List<dynamic>.from(json["doNotContact"].map((x) => x)),
frequencyRules: List<dynamic>.from(json["frequencyRules"].map((x) => x)),
);
Map<String, dynamic> toJson() => {
"isPublished": isPublished,
"dateAdded": dateAdded.toIso8601String(),
"dateModified": dateModified,
"createdBy": createdBy,
"createdByUser": createdByUser,
"modifiedBy": modifiedBy,
"modifiedByUser": modifiedByUser,
"id": id,
"points": points,
"color": color,
"fields": fields.toJson(),
"lastActive": lastActive,
"owner": owner,
"ipAddresses": List<dynamic>.from(ipAddresses.map((x) => x)),
"tags": List<dynamic>.from(tags.map((x) => x)),
"utmtags": utmtags,
"stage": stage,
"dateIdentified": dateIdentified,
"preferredProfileImage": preferredProfileImage,
"doNotContact": List<dynamic>.from(doNotContact.map((x) => x)),
"frequencyRules": List<dynamic>.from(frequencyRules.map((x) => x)),
};
}
class Fields {
Fields({
this.core,
this.social,
this.personal,
this.professional,
this.all,
});
All core;
Social social;
List<dynamic> personal;
List<dynamic> professional;
All all;
//Not the full code
I have this post request you can change it to your desire
this is Post Function
Future<ResponseModel> HTTPPOST<T>(String url, List<QueryModel> query,
var body, HeaderEnum headerType, ResponseEnum responseType) async {
try {
var response = await http.post(
UrlGenerator(url, query),
headers: headerGetter(headerType),
body: body,
);
return responseGetter<T>(responseType, response);
} catch (e) {
return ResponseModel(
isSuccess: false,
statusCode: "500",
data: null,
message: "خطایی در عملیات رخ داده است");
}
}
this is the responseGetter function
responseGetter<T>(ResponseEnum typeEnum, http.Response response) {
try {
switch (typeEnum) {
case ResponseEnum.ResponseModelEnum:
String data = utf8.decode(response.bodyBytes);
if (data == null || data.isEmpty)
return ResponseModel(
statusCode: "555",
isSuccess: false,
data: null,
);
return ResponseModel().fromJson(
json.decode(data),
);
default:
return response.bodyBytes;
}
} catch (e) {
return ResponseModel(
isSuccess: false,
statusCode: "500",
data: null,
message: "خطایی در عملیات رخ داده است");
}
}
and this is using post function in an example
Future<ResponseModel<CartHeader>> AddProduct(int productId, int qty) async {
var map = {"productId": productId, "qty": qty};
var json = jsonEncode(map);
var response = await HTTPPOST(
RoutingCart.POST_AddProduct,
[],
json,
HeaderEnum.BearerHeaderEnum,
ResponseEnum.ResponseModelEnum,
);
return ResponseModel<CartHeader>(
isSuccess: response.isSuccess,
statusCode: response.statusCode,
data: response.data,
message: response.message,
); }
My Json response status code is 200 and the object is also created but when I try to bind it with UI in ListView it doesn't show anything. I have used quicktype.io to parse my json data that I received from the API call.
Here's my API_manager.dart file:
import 'package:http/http.dart' as http;
import 'package:aritic/models/contactsModel.dart';
// ignore: camel_case_types
class API_Manager {
Future<ContactsModel> getContacts() async {
var client = http.Client();
var contactsModel;
String contacts_url =
'https://exampleapi.com';
String basicAuth = 'Basic auth key example';
try {
var response = await client.get(contacts_url,
headers: <String, String>{'authorization': basicAuth});
print(response.statusCode);
if (response.statusCode == 200) {
var jsonString = response.body;
var jsonMap = json.decode(jsonString);
contactsModel = contactsModel.fromJson(jsonMap);
}
} catch (Exception) {
return contactsModel;
}
return contactsModel;
}
}
My UI code:
import 'package:aritic/models/contactsModel.dart';
import 'package:aritic/services/api_manager.dart';
class ContactsPage extends StatefulWidget {
#override
_ContactsPageState createState() => _ContactsPageState();
}
class _ContactsPageState extends State<ContactsPage>
with SingleTickerProviderStateMixin {
Future<ContactsModel> _contactsModel;
bool isSearching = false;
TabController _tabController;
#override
void initState() {
// TODO: implement initState
super.initState();
_tabController = TabController(length: 2, initialIndex: 0, vsync: this);
_tabController.addListener(_handleTabIndex);
}
#override
void dispose() {
_tabController.removeListener(_handleTabIndex);
_tabController.dispose();
super.dispose();
}
void _handleTabIndex() {
setState(() {});
}
#override
Widget build(BuildContext context) {
_contactsModel = API_Manager().getContacts();
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Contacts'),
bottom: PreferredSize(
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
controller: _tabController,
isScrollable: true,
unselectedLabelColor: Colors.white.withOpacity(0.3),
indicatorColor: Colors.white,
tabs: [
Tab(
child: Text('Contacts'),
),
Tab(
child: Text('Companies'),
)
],
),
),
preferredSize: Size.fromHeight(40.0)),
actions: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: IconButton(
icon: Icon(Icons.search),
color: Colors.white,
onPressed: () {},
),
),
],
),
body: TabBarView(controller: _tabController, children: <Widget>[
Container(
height: double.infinity,
child: FutureBuilder<ContactsModel>(
future: _contactsModel,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
padding: const EdgeInsets.all(6),
itemCount: snapshot.data.contacts.length,
itemBuilder: (context, index) {
var contact = snapshot.data.contacts[index];
return Container(
height: 100,
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(contact.owner.username,
style: TextStyle(fontSize: 16))
],
),
);
});
} else
return Center(child: CircularProgressIndicator());
})),
Container(
height: double.infinity,
child: ListView(
padding: const EdgeInsets.all(6),
children: <Widget>[
InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => ViewCompany()));
},
child: Container(
height: 50,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Example company',
style: TextStyle(fontSize: 16),
),
Text(
'Example company',
style: TextStyle(fontSize: 14),
)
],
),
),
),
SizedBox(
height: 5,
),
InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => ViewCompany()));
},
child: Container(
height: 50,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'example',
style: TextStyle(fontSize: 16),
),
Text(
'example',
style: TextStyle(fontSize: 14),
)
],
),
),
),
],
)),
]),
floatingActionButton: _bottomButtons(),
));
}
Widget _bottomButtons() {
return _tabController.index == 0
? FloatingActionButton(
shape: StadiumBorder(),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return AddContacts();
}));
},
backgroundColor: Colors.cyan,
child: Icon(
Icons.person_add,
color: Colors.white,
))
: FloatingActionButton(
shape: StadiumBorder(),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return AddCompany();
}));
},
backgroundColor: Colors.cyan,
child: Icon(
Icons.add,
color: Colors.white,
),
);
}
}
Created model(contactsModel.dart):
import 'dart:convert';
ContactsModel contactsModelFromJson(String str) => ContactsModel.fromJson(json.decode(str));
String contactsModelToJson(ContactsModel data) => json.encode(data.toJson());
class ContactsModel {
ContactsModel({
this.total,
this.contacts,
});
String total;
Map<String, Contact> contacts;
factory ContactsModel.fromJson(Map<String, dynamic> json) => ContactsModel(
total: json["total"],
contacts: Map.from(json["contacts"]).map((k, v) => MapEntry<String, Contact>(k, Contact.fromJson(v))),
);
Map<String, dynamic> toJson() => {
"total": total,
"contacts": Map.from(contacts).map((k, v) => MapEntry<String, dynamic>(k, v.toJson())),
};
}
class Contact {
Contact({
this.isPublished,
this.dateAdded,
this.dateModified,
this.createdBy,
this.createdByUser,
this.modifiedBy,
this.modifiedByUser,
this.id,
this.points,
this.color,
this.fields,
this.lastActive,
this.owner,
this.ipAddresses,
this.tags,
this.utmtags,
this.stage,
this.dateIdentified,
this.preferredProfileImage,
this.doNotContact,
this.frequencyRules,
});
bool isPublished;
DateTime dateAdded;
DateTime dateModified;
int createdBy;
String createdByUser;
int modifiedBy;
String modifiedByUser;
int id;
int points;
dynamic color;
Fields fields;
dynamic lastActive;
Owner owner;
List<dynamic> ipAddresses;
List<Tag> tags;
List<dynamic> utmtags;
Stage stage;
DateTime dateIdentified;
dynamic preferredProfileImage;
List<dynamic> doNotContact;
List<dynamic> frequencyRules;
factory Contact.fromJson(Map<String, dynamic> json) => Contact(
isPublished: json["isPublished"],
dateAdded: DateTime.parse(json["dateAdded"]),
dateModified: json["dateModified"] == null ? null : DateTime.parse(json["dateModified"]),
createdBy: json["createdBy"],
createdByUser: json["createdByUser"],
modifiedBy: json["modifiedBy"] == null ? null : json["modifiedBy"],
modifiedByUser: json["modifiedByUser"] == null ? null : json["modifiedByUser"],
id: json["id"],
points: json["points"],
color: json["color"],
fields: Fields.fromJson(json["fields"]),
lastActive: json["lastActive"],
owner: Owner.fromJson(json["owner"]),
ipAddresses: List<dynamic>.from(json["ipAddresses"].map((x) => x)),
tags: List<Tag>.from(json["tags"].map((x) => Tag.fromJson(x))),
utmtags: List<dynamic>.from(json["utmtags"].map((x) => x)),
stage: Stage.fromJson(json["stage"]),
dateIdentified: DateTime.parse(json["dateIdentified"]),
preferredProfileImage: json["preferredProfileImage"],
doNotContact: List<dynamic>.from(json["doNotContact"].map((x) => x)),
frequencyRules: List<dynamic>.from(json["frequencyRules"].map((x) => x)),
);
Map<String, dynamic> toJson() => {
"isPublished": isPublished,
"dateAdded": dateAdded.toIso8601String(),
"dateModified": dateModified == null ? null : dateModified.toIso8601String(),
"createdBy": createdBy,
"createdByUser": createdByUser,
"modifiedBy": modifiedBy == null ? null : modifiedBy,
"modifiedByUser": modifiedByUser == null ? null : modifiedByUser,
"id": id,
"points": points,
"color": color,
"fields": fields.toJson(),
"lastActive": lastActive,
"owner": owner.toJson(),
"ipAddresses": List<dynamic>.from(ipAddresses.map((x) => x)),
"tags": List<dynamic>.from(tags.map((x) => x.toJson())),
"utmtags": List<dynamic>.from(utmtags.map((x) => x)),
"stage": stage.toJson(),
"dateIdentified": dateIdentified.toIso8601String(),
"preferredProfileImage": preferredProfileImage,
"doNotContact": List<dynamic>.from(doNotContact.map((x) => x)),
"frequencyRules": List<dynamic>.from(frequencyRules.map((x) => x)),
};
}
class ......{}
Output Screen: here
Json Viewer: here
Dart Devtools: here
This is probably not a problem with your json parsing, but a UI issue. Putting Columns or Rows inside ListViews or the opposite. If there was something wrong with the json, you'd be seeing the return Center(child: CircularProgressIndicator());,
But instead, you're getting an empty UI.
Check your debug console, if you have RenderView errors, or failed to layout render-box issues, this will be the cause of them.
Try using the property of shrink wrap in your lists:
ListView(shrinkwrap:true...
and
ListView.builder(shirinkrap:true...
Also, wrap your Row that inside the ListView with a LimitedBox
i.e
LimitedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [