I have been going through various articles on pagination in flutter but none of them seem to work out for me. The API endpoint I am working with looks like this
http://camx.heropp.com/api/connect?offset=0 (this is an example link and so it won't work) while the response I get when I make the request looks like this
{
"result": {
"connectUsers": [
{
"_id": "5f6a412d2ea9350017bec99f",
"userProfile": {
"rep_points": 0.75,
"visits": 0,
"bio": "Nothing for you ",
"gender": "Male",
"university": "University Of Technology",
"avatar": "https://camx.heroapp.com/5f6a412d2ea9350017bec99f"
},
"name": "Joseph Henshaw ",
"userTag": "bigjo",
"id": "5f6a412d2ea9350017bec99f",
"sameCampus": true
},
{
"_id": "5f6bbf1cbd5faa00170d92b1",
"userProfile": {
"rep_points": 0,
"visits": 0
},
"name": "John Doe",
"userTag": "#doee",
"id": "5f6bbf1cbd5faa00170d92b1",
"sameCampus": false
}
]
}
}
what i am trying to achieve is paginate the data coming from the api..the offset begins at 0 and increases with 10, i.e to get more data 0ffset=20..30..and so on
This is the request I am making to get the JSON response shown above
Future<void> fetchConnect() async {
var uri = Uri.parse('http://campusx.herokuapp.com/api/v1/users/connect');
uri = uri.replace(query: 'offset=$offsetNumber');
print(uri);
try {
final response = await http.get(
uri,
headers: {
HttpHeaders.authorizationHeader: "Bearer $userToken",
},
);
// List<Photo> fetchedPhotos = Photo.parseList(json.decode(response.body));
if (response.statusCode == 200 || response.statusCode == 201) {
print("IT works")
} else {
print(response.statusCode);
}
List<ConnectUsers> fetchedConnects =
ConnectUsers.parseList(json.decode(response.body));
setState(() {
connectMore = fetchedConnects.length == defaultConnectsPerPageCount;
loading = false;
offsetNumber = offsetNumber + 10;
connects.addAll(fetchedConnects);
});
} catch (e) {
setState(() {
loading = false;
error = true;
});
}
}
and this is how my UI for displaying the data fetched(the widget getConnect is placed in the body of my Scaffold
Widget getConnects() {
if (connects.isEmpty) {
if (loading) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator(),
));
} else if (error) {
return Center(
child: InkWell(
onTap: () {
setState(() {
loading = true;
error = false;
fetchConnects();
});
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Text("Error while loading connects, tap to try again"),
),
));
}
} else {
return ListView.builder(
itemCount: connects.length + (connectMore ? 10 : 0),
itemBuilder: (context, index) {
if (index == connects.length - nextPageThreshold) {
fetchConnects();
}
if (index == connects.length) {
if (error) {
return Center(
child: InkWell(
onTap: () {
setState(() {
loading = true;
error = false;
fetchConnects();
});
},
child: Padding(
padding: const EdgeInsets.all(16),
child:
Text("Error while loading connects, tap to try agin"),
),
));
} else {
return Center(
child: Padding(
padding: const EdgeInsets.all(8),
child: CircularProgressIndicator(),
));
}
}
// final Photo photo = photos[index];
final ConnectUsers connect = connects[index];
return Card(
child: Column(
children: <Widget>[
// Image.network(
// connect.name.connectUsers[index].userProfile.avatar,
// fit: BoxFit.fitWidth,
// width: double.infinity,
// height: 160,
// ),
Padding(
padding: const EdgeInsets.all(16),
child: Text(connect.name,
// connect
// .result.connectUsers[index].userProfile.university,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16)),
),
],
),
);
});
}
return Container();
}
this is the error i get when using this code
The getter 'isEmpty' was called on null.
Receiver: null
Tried calling: isEmpty
Looks like you are calling connects.isEmpty in your widget but you didn't initialise connects -> connects is null so you get the error above.
You could do a null check or initialise connects
...
// initialise so connects is not null
connects = []
...
Widget getConnects() {
if (connects.isEmpty) {
if (loading) {
return Center(
(...)
Widget getConnects() {
// do a null check before calling member
if (connects == null || connects.isEmpty) {
if (loading) {
return Center(
(...)
I've solved the problem, this is the way i used
this is my model.dart class
import 'package:flutter/foundation.dart';
ConnectModel payloadFromJson(String str) =>
ConnectModel.fromJson(json.decode(str));
String payloadToJson(ConnectModel data) => json.encode(data.toJson());
class ConnectModel {
ConnectModel({
this.result,
});
Result result;
factory ConnectModel.fromJson(Map<String, dynamic> json) => ConnectModel(
result: Result.fromJson(json["result"]),
);
Map<String, dynamic> toJson() => {
"result": result.toJson(),
};
}
class Result {
Result({
this.connect,
});
List<Connect> connect;
factory Result.fromJson(Map<String, dynamic> json) => Result(
connect: List<Connect>.from(
json["connectUsers"].map((x) => Connect.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"connectUsers": List<dynamic>.from(connect.map((x) => x.toJson())),
};
}
class Connect with ChangeNotifier {
Connect({
this.id,
this.name,
this.userTag,
this.university,
this.checkIsFollowing,
this.avatar,
});
String name;
String userTag;
String id;
String university;
String avatar;
bool checkIsFollowing;
factory Connect.fromJson(Map<String, dynamic> json) => Connect(
id: json["_id"],
name: json["name"],
userTag: json["userTag"],
university: json["userProfile"]["university"],
checkIsFollowing: json["checkIsFollowing"],
avatar: json["userProfile"]["avatar"],
);
Map<String, dynamic> toJson() => {
"_id": id,
"name": name,
"userTag": userTag,
"userProfile"
"university": university,
"userProfile"
"avatar": avatar,
"checkIsFollowing": checkIsFollowing
};
}
this is my method, where offsetNumber is initialized to 0 by default
List<Connect> mainList = [];
List<String> nameList = [];
Future<List<Connect>> getConnectionsList(var offsetNumber) async {
var uri = "http://campusx.herokuapp.com/api/v1/users/connect?$offsetNumber";
var token = "pass_your_token_here";
try {
final response =
await http.get(uri, headers: {"Authorization": "Bearer $token"});
if (response.statusCode == 200) {
var data = json.decode(response.body);
var d = data['result']['connectUsers'];
for (var u in d) {
Connect c = Connect.fromJson(u);
mainList.add(c);
}
setState(() {
nameList = mainList.map((e) => e.name).toList();
//add other parameters gotten from the endpoint here, i used name only for test purposes
});
} else {
print(response.body);
}
} catch (e) {
print(e);
}
return mainList;
}
this is the ListView builder i displayed the names in
ListView.builder(
itemCount: mainList.length,
itemBuilder: (_, index) {
return Container(
height: 120,
child: Text(
nameList[index],
style: TextStyle(fontSize: 39),
),
);
},
)
then either onClick or a button or when the list hits the bottom of your phone screen, you'd setState (() {}); and increase the size of the offsetNumber. Something like this:
setState(() {
offsetNumber = offsetNumber + 10;
future = getConnectionsList(offsetNumber);
});
Related
how do I call the API data into the dropdown I have done several times, when I used local data in the form of a String list I managed to call the data, but when I tried to call from the API data the result was empty and the results in the dropdown did not display any data
Future<List<Agama>> getAgama() async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
final response = await http.get(
Uri.parse(
'$url/auth/mhs_siakad/dosen',
),
headers: {
'Authorization': 'Bearer $token',
},
);
print(response.statusCode);
print(response.body);
if (response.statusCode == 200) {
final result =
json.decode(response.body)['nm_agama'] as Map<String, dynamic>;
UserBiodata agama = UserBiodata.fromJson(result);
List<Agama> l = agama.data ?? [];
return l;
} else {
throw Exception();
}
}
in widget
List<Agama>? religion = [];
#override
void initState() {
super.initState();
BiodataProvider().getAgama();
}
var dropdownAgama;
...
FutureBuilder(
future: BiodataProvider().getBiodata(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(' ini Agama $religion');
return Column(
children: [
Text(
snapshot.data!.data!.name.toString(),
style: bold5,
),
DropdownButton(
hint: const Text('Religion'),
items: religion!.map((item) {
return DropdownMenuItem(
value: item.nmAgama.toString(),
child: Text(item.idAgama.toString()),
);
}).toList(),
onChanged: (newVal) {
setState(() {
dropdownAgama = newVal;
});
},
value: dropdownAgama,
),
],
);
} else {
return const Text('No Data');
}
}),
...
this is the model i am using which is converted from API
class Agama {
String? id;
String? idAgama;
String? nmAgama;
String? createdAt;
String? updatedAt;
dynamic createdBy;
dynamic updatedBy;
Agama(
{this.id,
this.idAgama,
this.nmAgama,
this.createdAt,
this.updatedAt,
this.createdBy,
this.updatedBy});
Agama.fromJson(Map<String, dynamic> json) {
id = json['id'];
idAgama = json['id_agama'];
nmAgama = json['nm_agama'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
createdBy = json['created_by'];
updatedBy = json['updated_by'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['id_agama'] = idAgama;
data['nm_agama'] = nmAgama;
data['created_at'] = createdAt;
data['updated_at'] = updatedAt;
data['created_by'] = createdBy;
data['updated_by'] = updatedBy;
return data;
}
}
it looks like you are not assigning your api data to the
List<Agama>? religion;
you can solve this by either mapping your snapshot data directly:
#override
void initState() {
super.initState();
BiodataProvider().getAgama();
}
var dropdownAgama;
...
FutureBuilder(
future: BiodataProvider().getBiodata(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(' ini Agama $religion');
return Column(
children: [
Text(
snapshot.data!.data!.name.toString(),
style: bold5,
),
DropdownButton(
hint: const Text('Religion'),
items: snapshot.data!.data!.map((item) {
return DropdownMenuItem(
value: item.nmAgama.toString(),
child: Text(item.idAgama.toString()),
);
}).toList(),
onChanged: (newVal) {
setState(() {
dropdownAgama = newVal;
});
},
value: dropdownAgama,
),
],
);
} else {
return const Text('No Data');
}
}),
or
by assigning the snapshot data to your list:
List<Agama>? religion = [];
#override
void initState() {
super.initState();
BiodataProvider().getAgama();
}
var dropdownAgama;
...
FutureBuilder(
future: BiodataProvider().getBiodata(),
builder: (context, snapshot) {
if (snapshot.hasData) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState((){
religion = snapshot.data!.data;
})
});
print(' ini Agama $religion');
return Column(
children: [
Text(
snapshot.data!.data!.name.toString(),
style: bold5,
),
DropdownButton(
hint: const Text('Religion'),
items: religion!.map((item) {
return DropdownMenuItem(
value: item.nmAgama.toString(),
child: Text(item.idAgama.toString()),
);
}).toList(),
onChanged: (newVal) {
setState(() {
dropdownAgama = newVal;
});
},
value: dropdownAgama,
),
],
);
} else {
return const Text('No Data');
}
}),
hope this helps!
Solution
In Controller Class,
Initialize your Future Method as your Model : Future<List<LeaveModel>> getLeaves(){}
create a List of Obj for Custom Class like this :
List<LeaveModel> leaveTypeObjList = [];
After fetching data from API add the datas in obj list via Object
type :LeaveModel leaveTypeObj = LeaveModel(leaveTypeResponseList[i]["LEAVE_TYPE_EN"]);
And in loop add it to your obj list: leaveTypeObjList.add(leaveTypeObj);
** In widget class at AsyncSnapshot use your List of Generics like this :
AsyncSnapshot<List<LeaveModel>>.
Demo Code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main(List<String> args) {
runApp(MyHome());
}
class MyHome extends StatelessWidget {
const MyHome({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<String> leaveTypeList = [];
int currentLeaveType = 0;
String? value;
String? selectedLeaveType;
late Future<List<LeaveModel>> futureLeaves;
#override
void initState() {
super.initState();
futureLeaves = FoodTypeController().getLeaves();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: double.infinity,
width: double.infinity,
child: Row(
children: [
Text(
"Leave Type = ",
style: TextStyle(fontSize: 24.0),
),
FutureBuilder(
future: futureLeaves,
builder: ((BuildContext context,
AsyncSnapshot<List<LeaveModel>> snapshot) {
if (snapshot.hasData) {
leaveTypeList = [];
List<LeaveModel>? LeaveTypes = snapshot.data;
for (int i = 0; i < LeaveTypes!.length; i++) {
leaveTypeList.add(LeaveTypes[i].leaveTypes!);
}
return SizedBox(
width: 200,
height: 50,
child: Container(
padding: EdgeInsets.only(left: 2.0, right: 4.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.black, width: 1)),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
borderRadius: BorderRadius.circular(12),
value: currentLeaveType == null? null: leaveTypeList[currentLeaveType],
icon: Icon(Icons.arrow_drop_down_rounded),
items: leaveTypeList.map(buildMenuItem).toList(),
onChanged: (value) => setState(() {
currentLeaveType = leaveTypeList.indexOf(value.toString());
this.value = value;
selectedLeaveType = this.value;
}),
),
),
),
);
} else if (snapshot.hasError) {
return const Text("Error to load Snapshot");
} else {
return const CircularProgressIndicator();
}
})),
],
),
),
);
}
DropdownMenuItem<String> buildMenuItem(String item) => DropdownMenuItem(
value: item,
child: Text(
item.toString(),
style: TextStyle(fontWeight: FontWeight.w400, fontSize: 24.0),
),
);
}
class LeaveModel {
String? leaveTypes;
LeaveModel(this.leaveTypes);
}
class FoodTypeController {
Future<List<LeaveModel>> getLeaves() async {
List<LeaveModel> leaveTypeObjList = [];
String url = "";
http.Response response =
await http.post(Uri.parse(url), body: {"user": "", "pass": ""});
if (response.statusCode == 200) {
List leaveTypeResponseList = jsonDecode(response.body);
for (int i = 0; i < leaveTypeResponseList.length; i++) {
LeaveModel leaveTypeObj =
LeaveModel(leaveTypeResponseList[i]["LEAVE_TYPE"]);
leaveTypeObjList.add(leaveTypeObj);
}
return leaveTypeObjList;
} else {
throw Exception("Error to load data in Leave Controller");
}
}
}
Response
Visual
fetchData is my function from where I call the API and put the data into an object, which is UserModel Somehow, it is working perfectly. But I want to put my data into a list because I want to make a search function where I can search by name. Look into my code, which will help you to understand.
Future<UserModel>? futureUser;
Future<UserModel>? fetchData() async {
final response =
await http.get(Uri.parse('https://reqres.in/api/users?page=2'));
print('This is Response: $response');
if (response.statusCode == 200) {
// this is a way which I've tried already and it works
// return UserModel.fromJson(jsonDecode(response.body));
} else {
return throw Exception('Failed to load album');
}
}
But I want to put the data into a list and make the search available. Like if i put some name like r+a+b+b+i, it will show the matching name from the API.
I have tried this but I am not clear about the consepet. I am not familiar with how to manipulate the JSON data in a list or object or how to convert an object into a list.
List<UserModel>? userList = [];
Future<UserModel>? fetchData() async {
final response =
await http.get(Uri.parse('https://reqres.in/api/users?page=2'));
print('This is Response: $response');
if (response.statusCode == 200) {
// var result= UserModel.fromJson(jsonDecode(response.body));
// print('this is result $userList');
return userList.add(UserModel.fromJson(jsonDecode(response.body)));
// this is an way which i tried already and its works
// return UserModel.fromJson(jsonDecode(response.body));
} else {
return throw Exception('Failed to load album');
}
}
This is my whole code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:learning_1ui_6228/utilities/app_colors.dart';
import 'package:learning_1ui_6228/utilities/helper.dart';
import 'package:learning_1ui_6228/utilities/widgets/app_line.dart';
import 'package:learning_1ui_6228/utilities/widgets/list_tile_widget.dart';
import 'package:learning_1ui_6228/views/nav_pages/profile_page.dart';
import 'package:http/http.dart' as http;
import '../model/UserModel.dart';
class FirstScreen extends StatefulWidget {
const FirstScreen({Key? key}) : super(key: key);
#override
State<FirstScreen> createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
Future<UserModel>? futureUser;
TextEditingController textController = TextEditingController();
// List<UserModel> userList=[];
#override
void initState() {
// searchedList = userList;
futureUser = fetchData();
super.initState();
}
List<UserModel>? userList = [];
Future<UserModel>? fetchData() async {
final response =
await http.get(Uri.parse('https://reqres.in/api/users?page=2'));
print('This is Response: $response');
if (response.statusCode == 200) {
// var result= UserModel.fromJson(jsonDecode(response.body));
// print('this is result $userList');
return userList.add(UserModel.fromJson(jsonDecode(response.body)));
// this is an way which i tried already and its works
// return UserModel.fromJson(jsonDecode(response.body));
} else {
return throw Exception('Failed to load album');
}
}
List<UserModel> searchedList = [];
void searchUser(String enteredData){
print('entered word + ${enteredData}');
searchedList = [];
for(int i=0; i<userList!.length; i++){
if(userList[i].data![i].firstName!.toLowerCase().contains(enteredData.toLowerCase())){
searchedList.add(userList![i]);
}
}
}
#override
Widget build(BuildContext context) {
//print('user list data + $searchedList');
return SafeArea(
child: Scaffold(
backgroundColor: Color(0xfff8f8fa),
body: Column(
children: [
//1st Section
Container(
height: HelperClass.h250,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: AppColors.gradientColor,
),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(HelperClass.r10),
bottomLeft: Radius.circular(HelperClass.r10))),
child: Column(
children: <Widget>[
//Text and cross button
Container(
margin: EdgeInsets.only(
left: HelperClass.w10,
right: HelperClass.w10,
top: HelperClass.h20),
height: HelperClass.h50,
// color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
child: IconButton(
onPressed: () {},
icon: Icon(
Icons.clear,
color: Colors.white,
size: 30,
))),
Expanded(
child: Container(
margin: EdgeInsets.only(right: 30),
alignment: Alignment.center,
// color: Colors.lightBlueAccent,
child: Text(
'Search',
style: TextStyle(
fontSize: HelperClass.t25,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
],
),
),
SizedBox(
height: HelperClass.h25,
),
//Search Bar
Container(
margin: EdgeInsets.only(
left: HelperClass.w10, right: HelperClass.w10),
//color: Colors.amber,
height: HelperClass.h70,
width: double.infinity,
child: TextField(
controller: textController,
onChanged: (name) {
setState(() {
searchUser(name);
});
},
decoration: InputDecoration(
prefix: Icon(
Icons.search,
size: 26,
),
suffix: IconButton(
onPressed: () {
setState(() {
textController.clear();
searchedList = userList;
});
},
icon: Icon(
Icons.clear,
size: 26,
),
),
hintText: 'Search',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(3),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.white,
),
),
),
],
),
),
// List View
Expanded(
child: FutureBuilder<UserModel>(
future: futureUser,
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data!.data!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfilePage(
userName:snapshot.data!.data![index].firstName??'',
followers: snapshot.data!.data![index].id.toString(),
address: snapshot.data!.data![index].email.toString(),
following: snapshot.data!.data![index].lastName.toString(),
imageUrl: snapshot.data!.data![index].avatar.toString(),
),
));
},
child: ListTileWidgets(
following: snapshot.data!.data![index].lastName.toString(),
address: snapshot.data!.data![index].email.toString(),
imageUrl:snapshot.data!.data![index].avatar.toString(),
name: snapshot.data!.data![index].firstName??'',
followersCount:
'Followers: ${snapshot.data!.data![index].id.toString()}',
iconWidget: Icon(
Icons.person_add_alt_outlined,
color: Colors.red,
size: 25,
),
),
),
AppLine(
paddingLeft: 10,
paddingRight: 10,
heightLine: 1,
lineColor: Colors.grey),
],
),
);
},
);
}
else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return Center(child: CircularProgressIndicator());
}),
),
],
),
),
);
}
}
This is my UserModel class
import 'dart:convert';
UserModel userModelFromJson(String str) => UserModel.fromJson(json.decode(str));
String userModelToJson(UserModel data) => json.encode(data.toJson());
class UserModel {
UserModel({
this.page,
this.perPage,
this.total,
this.totalPages,
this.data,
this.support,});
UserModel.fromJson(dynamic json) {
page = json['page'];
perPage = json['per_page'];
total = json['total'];
totalPages = json['total_pages'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(Data.fromJson(v));
});
}
support = json['support'] != null ? Support.fromJson(json['support']) : null;
}
int? page;
int? perPage;
int? total;
int? totalPages;
List<Data>? data;
Support? support;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['page'] = page;
map['per_page'] = perPage;
map['total'] = total;
map['total_pages'] = totalPages;
if (data != null) {
map['data'] = data?.map((v) => v.toJson()).toList();
}
if (support != null) {
map['support'] = support?.toJson();
}
return map;
}
}
Support supportFromJson(String str) => Support.fromJson(json.decode(str));
String supportToJson(Support data) => json.encode(data.toJson());
class Support {
Support({
this.url,
this.text,});
Support.fromJson(dynamic json) {
url = json['url'];
text = json['text'];
}
String? url;
String? text;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['url'] = url;
map['text'] = text;
return map;
}
}
Data dataFromJson(String str) => Data.fromJson(json.decode(str));
String dataToJson(Data data) => json.encode(data.toJson());
class Data {
Data({
this.id,
this.email,
this.firstName,
this.lastName,
this.avatar,});
Data.fromJson(dynamic json) {
id = json['id'];
email = json['email'];
firstName = json['first_name'];
lastName = json['last_name'];
avatar = json['avatar'];
}
int? id;
String? email;
String? firstName;
String? lastName;
String? avatar;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['email'] = email;
map['first_name'] = firstName;
map['last_name'] = lastName;
map['avatar'] = avatar;
return map;
}
}
this is my api link
https://reqres.in/api/users?page=2
No need to add UserModel to list.
change this
class _FirstScreenState extends State<FirstScreen> {
UserModel? usermodel;
List<Data?> searchResult= [];
change your fetch data . this will return UserModel as result.
Future<UserModel?> fetchData() async {
final response =
await http.get(Uri.parse('https://reqres.in/api/users?page=2'));
print('This is Response: $response');
if (response.statusCode == 200) {
return UserModel.fromJson(jsonDecode(response.body));
} else {
return throw Exception('Failed to load album');
}
}
create init function to set your initial data
void initFunction() async {
UserModel data = await fetchData(); // you have to await until get the response
//then setState to local variable so it can display to widget
// if you skip this , your usermodel is null
setState ({
usermodel = data ;
});
}
then in your initState
#override
void initState() {
initFunction();
super.initState();
}
usermodel.data consist of data user.
to then you can apply logic to search user from the list.
void searchUser(String enteredData){
List<Data?> temp = [];
for(int i=0; i<usermodel.data.length; i++){
if(enteredData.toLowerCase() == usermodel.data[i].firstName.toLowerCase()){
temp.add(usermodel.data[i];
}
}
// you need to setState again
setState({
searchResult = temp;
});
}
last in you can acces the data in userModel
#override
Widget build(BuildContext context) {
//print('user list data + $searchedList');
return SafeArea(
child: Scaffold(
backgroundColor: Color(0xfff8f8fa),
body: Column(
children: [
Text('${usermodel.data.length}'), /// number all list user
Text('${searchResult.length}'), /// number search user
// now you have list all user
// and all searched list user
// additional, you need to add logic when query is empty
.................
maybe there error in null-safety, please debug first.
You can simply do this
var myList= []; // declare an empty list
if (response.statusCode == 200) {
var result= UserModel.fromJson(jsonDecode(response.body));
if(result != null){
myList.clear();
myList.addAll(result);
}
}
there is a problem. I have SOMETIMES no download icon disappears. Or to put it another way, instead of "No more entries", I see a loading circle.
This happens depending on how much data I received. For example, for 5 records there will be an endless download, and for 6 records it will display "There are no more records"
Tell me, what's the problem?
class _MyHomePageState extends State<MyHomePage> {
var ressultat = json.decode(MyApp.resultat)['Transactions'];
final controller = ScrollController();
bool hasMore = true;
int page = 2;
bool isLoading = false;
#override
void initState() {
super.initState();
// fetch();
controller.addListener(() {
if(controller.position.maxScrollExtent == controller.offset) {
fetch();
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Future fetch() async {
if(isLoading) return;
isLoading = true;
var token = LoginClass.resJson;
const limit = 9;
try {
final response = await http.post(
Uri.parse(Config.urlReport),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
"Authorization": "Bearer $token"
},
body: json.encode({
"Filters": {
"DateFrom" : MyApp.dateFrom,
"DateTo" : MyApp.dateEnd,
"AmountFrom": MyApp.sumFrom,
"AmountTo": MyApp.sumTo,
"SenderPaymentSystemId": MyApp.dropdownvalue,
"SenderRequisite": MyApp.phoneNum},
"Sorting": {
"Field": MyApp.isCheckedFieldPicker,
"Order": MyApp.isCheckedOrderPicker},
"Pagination": {
"PageNumber": page,
"PageSize": limit
}
})
);
if (response.statusCode == 200) {
var reportResult = (response.body);
print(reportResult);
final List ress = json.decode(reportResult)['Transactions'];
setState(() {
page++;
isLoading = false;
if(MyApp.rowNumber!.length < limit) {
hasMore = false;
}
MyApp.rowNumber?.addAll(ress.map<String>((item) {
final rowNumber = item['RowNumber'].toString();
return rowNumber;
}));
MyApp.walletName?.addAll(ress.map<String>((item) {
final walletName = item['WalletName'].toString();
return walletName;
}));
MyApp.payerPhoneNumber?.addAll(ress.map<String>((item) {
final phoneNumber = item['SenderRequisite'].toString();
return phoneNumber;
}));
MyApp.createdDate?.addAll(ress.map<String>((item) {
final createdDate = item['CreatedDate'].toString();
return createdDate;
}));
MyApp.amount?.addAll(ress.map<String>((item) {
final amount = item['Amount'].toString();
return amount;
}));
});
print(MyApp.createdDate.runtimeType);
} else {
var res = response.statusCode;
print(res);
}
} catch (error) {
print(error);
}
}
Here is all the necessary code, if you need something else, please let me know
child: ListView.builder(
controller: controller,
padding: EdgeInsets.all(5),
itemCount: MyApp.rowNumber!.length + 1,
itemBuilder: (context, index) {
if (index < MyApp.rowNumber!.length) {
return Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(color: Colors.black)
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Container(
margin: EdgeInsets.all(5),
child: Text(' ${MyApp.rowNumber?[index]}', style: TextStyle(fontSize: 15),),
)
],
),
Column(
children: [
Container(
margin: EdgeInsets.all(5),
width: 80,
child: Text('${MyApp.createdDate?[index].replaceFirst(RegExp('T'), '')}', style: TextStyle(fontSize: 15),),
)
],
),
Column(
children: [
Container(
margin: EdgeInsets.all(5),
child: Column(
children: [
Text('${MyApp.walletName?[index]}', style: TextStyle(fontSize: 15),),
Text('${MyApp.payerPhoneNumber?[index]}', style: TextStyle(fontSize: 15),),
],
),
)
],
),
Column(
children: [
Container(
margin: EdgeInsets.all(5),
child: Text('${MyApp.amount?[index]}', style: TextStyle(fontSize: 15),),
)
],
)
],
),
),
],
)
);
} else {
return Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Center(
child: hasMore
? const CircularProgressIndicator()
: const Text('Список полностью загружен')
,),
);
}
}
)
UPD: I clarify the question: My first request was sent with a limit of 9 entries, however, if there are less than 9 entries, then there will be an endless download. How to fix it?
you are not setting isLoading as false for HTTP request's response other than 200 and for error.
Future fetch() async {
if(isLoading) return;
isLoading = true;
var token = LoginClass.resJson;
const limit = 9;
try {
final response = await http.post(
Uri.parse(Config.urlReport),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
"Authorization": "Bearer $token"
},
body: json.encode({
"Filters": {
"DateFrom" : MyApp.dateFrom,
"DateTo" : MyApp.dateEnd,
"AmountFrom": MyApp.sumFrom,
"AmountTo": MyApp.sumTo,
"SenderPaymentSystemId": MyApp.dropdownvalue,
"SenderRequisite": MyApp.phoneNum},
"Sorting": {
"Field": MyApp.isCheckedFieldPicker,
"Order": MyApp.isCheckedOrderPicker},
"Pagination": {
"PageNumber": page,
"PageSize": limit
}
})
);
if (response.statusCode == 200) {
var reportResult = (response.body);
print(reportResult);
final List ress = json.decode(reportResult)['Transactions'];
setState(() {
page++;
isLoading = false;
if(MyApp.rowNumber!.length < limit) {
hasMore = false;
}
MyApp.rowNumber?.addAll(ress.map<String>((item) {
final rowNumber = item['RowNumber'].toString();
return rowNumber;
}));
MyApp.walletName?.addAll(ress.map<String>((item) {
final walletName = item['WalletName'].toString();
return walletName;
}));
MyApp.payerPhoneNumber?.addAll(ress.map<String>((item) {
final phoneNumber = item['SenderRequisite'].toString();
return phoneNumber;
}));
MyApp.createdDate?.addAll(ress.map<String>((item) {
final createdDate = item['CreatedDate'].toString();
return createdDate;
}));
MyApp.amount?.addAll(ress.map<String>((item) {
final amount = item['Amount'].toString();
return amount;
}));
});
print(MyApp.createdDate.runtimeType);
} else {
var res = response.statusCode;
isLoading = false;
setState(() {});
print(res);
}
} catch (error) {
isLoading = false;
setState(() {});
print(error);
}
}
I want to use my response data show in the widget. I post the data to my API then I get the return result.
My response.body is like this:
{
"responses": [
{
"Contents": {
"Images": [
{"url":"https:URL"},
{"url":"https:URL"},
],
"Ages": [
{"age":"33"},
{"age":"34"},
],
"Labels":[
{"label":"game"}
]
}
}
]
}
My question is how to get the "Images", "Ages", and "Labels" details? I want to use these detail and put in my widget. Can I know how to do it?
It's not mandatory to make full models in Dart for decoding JSON.
You can simply do:
// Requires import
import 'dart:convert';
// Do like this
var data = jsonDecode(jsonString);
Now data will automatically act as a map.
For example, if you want to access the label in the JSON example you provided, you can simply do:
data['responses'][0]['Contents']['Labels'][0]['label'].toString();
Coming to the actual part,
You need to shape your widgets according to your needs.
Create a Stateless or StatefulWidget whatever suits your needs and start laying out the design.
According to the JSON example that you posted, you need to show a list of images. So, you would probably generate a list of the Widgets based on the URLs and provide the list to perhaps a GridView in your build method.
Check this example I made: DartPad - JSON Widget example
Edit:
I used the exact JSON response you posted. It doesn't give me any errors.
class _MyWidgetState extends State<MyWidget> {
final String mJson = '''
{
"responses": [
{
"Contents": {
"Images": [
{"url":"https:URL"},
{"url":"https:URL"}
],
"Ages": [
{"age":"33"},
{"age":"34"}
],
"Labels":[
{"label":"game"}
]
}
}
]
}
''';
bool _loading = false;
List<String> _infos = [];
Widget _loadingBar() {
return SizedBox.fromSize(
size: Size.fromRadius(30),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('MY APPS'), actions: <Widget>[
_loading
? _loadingBar()
: IconButton(
icon: Icon(Icons.send),
onPressed: () => {
submit(context)
.then((res) => setState(() => _loading = false))
}),
]),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(_infos.length, (i) => Text(_infos[i]))),
));
}
Future<void> submit(BuildContext context) async {
setState(() => _loading = true);
await Future.delayed(const Duration(seconds: 3));
var data = jsonDecode(mJson);
print(data);
_infos.add(data['responses'][0].toString());
_infos.add(data['responses'][0]['Contents'].toString());
_infos.add(data['responses'][0]['Contents']['Images'][0]['url'].toString());
_infos.add(data['responses'][0]['Contents']['Ages'][0]['age'].toString());
}
}
Note: The code you posted later shouldn't work as you have declared non-final variables in a StatelessWidget class and also using setState. Also, in your Future submit(BuildContext context) you were supposed to jsonDecode(res) not demoJson. demoJson was not assigned. See the example I posted. I changed it to a stateful widget. I am having no trouble accessing any of the fields in the JSON example you gave. Your JSON example is a bit faulty though, it has extra ',' that might give error while decoding.
Just check the below example that i have made :
Follwing is the json file that you provided i have parsed locally.
{
"responses": [{
"Contents": {
"Images": [{
"url": "https:URL"
},
{
"url": "https:URL"
}
],
"Ages": [{
"age": "33"
},
{
"age": "34"
}
],
"Labels": [{
"age": "33"
}
]
}
}]
}
Model class created for the json file:
// To parse this JSON data, do
//
// final response = responseFromJson(jsonString);
import 'dart:convert';
Response responseFromJson(String str) => Response.fromJson(json.decode(str));
String responseToJson(Response data) => json.encode(data.toJson());
class Response {
List<ResponseElement> responses;
Response({
this.responses,
});
factory Response.fromJson(Map<String, dynamic> json) => Response(
responses: List<ResponseElement>.from(json["responses"].map((x) => ResponseElement.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"responses": List<dynamic>.from(responses.map((x) => x.toJson())),
};
}
class ResponseElement {
Contents contents;
ResponseElement({
this.contents,
});
factory ResponseElement.fromJson(Map<String, dynamic> json) => ResponseElement(
contents: Contents.fromJson(json["Contents"]),
);
Map<String, dynamic> toJson() => {
"Contents": contents.toJson(),
};
}
class Contents {
List<Image1> images;
List<Age> ages;
List<Age> labels;
Contents({
this.images,
this.ages,
this.labels,
});
factory Contents.fromJson(Map<String, dynamic> json) => Contents(
images: List<Image1>.from(json["Images"].map((x) => Image1.fromJson(x))),
ages: List<Age>.from(json["Ages"].map((x) => Age.fromJson(x))),
labels: List<Age>.from(json["Labels"].map((x) => Age.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"Images": List<dynamic>.from(images.map((x) => x.toJson())),
"Ages": List<dynamic>.from(ages.map((x) => x.toJson())),
"Labels": List<dynamic>.from(labels.map((x) => x.toJson())),
};
}
class Age {
String age;
Age({
this.age,
});
factory Age.fromJson(Map<String, dynamic> json) => Age(
age: json["age"],
);
Map<String, dynamic> toJson() => {
"age": age,
};
}
class Image1 {
String url;
Image1({
this.url,
});
factory Image1.fromJson(Map<String, dynamic> json) => Image1(
url: json["url"],
);
Map<String, dynamic> toJson() => {
"url": url,
};
}
This is the main UI file where you fetch and show the data, I have show your data in the list view.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sample_project_for_api/model.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool isLoading = false;
List<Age> ages = List();
List<Age> labels = List();
List<Image1> image = List();
// here i have taken the json locally which you posted on stack
Future<String> loadFromAssets() async {
return await rootBundle.loadString('json/parse.json');
}
#override
void initState() {
super.initState();
givenFunction();
}
Future givenFunction() async {
setState(() {
isLoading = true;
});
//http.Response response = await http.post(url, body: json.encode(data));
// you can make the http call above just uncomment is and comment the below line
String jsonString = await loadFromAssets();
// Here you can just replace down your response.body with jsonString
final response = responseFromJson(jsonString);
print(response.responses[0].contents.ages[0].age);
//ages =response.responses[0].contents.
for (int i = 0; i < response.responses[0].contents.ages.length; i++) {
ages.add(response.responses[0].contents.ages[i]);
}
for (int i = 0; i < response.responses[0].contents.images.length; i++) {
image.add(response.responses[0].contents.images[i]);
}
for (int i = 0; i < response.responses[0].contents.labels.length; i++) {
labels.add(response.responses[0].contents.labels[i]);
}
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: isLoading
? CircularProgressIndicator()
: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(
width: 400,
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: ages.length,
itemBuilder: (BuildContext context, int index) {
return Container(
width: 100,
height: 100,
child: Card(
elevation: 10,
child: Center(child: Text(ages[index].age)),
),
);
},
),
),
Container(
width: 500,
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: image.length,
itemBuilder: (BuildContext context, int index) {
return Container(
width: 100,
height: 100,
child: Card(
elevation: 10,
child: Center(child: Text(image[index].url)),
),
);
},
),
),
Container(
width: 500,
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: labels.length,
itemBuilder: (BuildContext context, int index) {
return Container(
width: 100,
height: 100,
child: Card(
elevation: 10,
child: Center(child: Text(labels[index].age)),
),
);
},
),
)
],
),
),
)),
);
}
}
void main() {
runApp(HomePage());
}
just taken a screen shot:
Just check it and let me know.
How to make scroll control by sending the next page parameter as pageno in url. I have url where I am sending the pageno as pageno='', and to get the next page I need to pass the data which is returned from the future fetchpost. For example I have initial url as
"https://example.org/api.php&action=query&gcmtitle=Category:$cname&pilimit=max&generator=categorymembers&format=json&gcmcontinue=$pageno where pageno=''
and second url to get next page content is
https://example.org/api.php&action=query&gcmtitle=Category:$cname&pithumbsize=600&generator=categorymembers&format=json&gcmcontinue=$pageno
where pageno='some string value'.
I have written code but the when I scroll to the end of the page I am passing next page data to url and it wipes out the data of first page and displays next page page content list on it instead continuing with the list below the firstpage.
Below is my code please help me with the issue.
class Herbs extends StatefulWidget {
final String title;
Herbs(this.title);
#override
_HerbsState createState() => new _HerbsState();
}
class _HerbsState extends State<Herbs> {
List<Herbslist> herbslist = [];
var cname;
var gcm;
var pageno = '';
ScrollController _scrollController = new ScrollController();
#override
void initState() {
fetchPost(pageno);
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
setState(() {
pageno = gcm;
fetchPost(pageno);
});
}
});
super.initState();
}
Future<Herbslist> fetchPost(String pageno) async {
final response = await http.get(
'https://example.org/api.php?action=query&gcmtitle=Category:$cname&pilimit=max&prop=pageimages&pithumbsize=600&generator=categorymembers&format=json&gcmcontinue=$pageno'
);
if (response.statusCode == 200) {
if(this.mounted){
var jsonData = json.decode(response.body);
gcm = jsonData["continue"]["gcmcontinue"];
}
} else {
print(Exception);
throw (e) {
print("Exception thrown: $e");
Exception(e);
};
}
return Herbslist.fromJson(json.decode(response.body));
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
cname = widget.title;
return new Scaffold(
appBar: AppBar(
title: Align(
alignment: Alignment(-0.2, 0.3),
child: Text(
cname,
),
),
),
body: Center(
child: FutureBuilder<Herbslist>(
future: fetchPost(gcm),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
controller: _scrollController,
itemCount: snapshot.data.query.pages.length,
itemBuilder: (BuildContext context, int index) {
var img = snapshot.data.query.pages.values.toList()[index].thumbnail.source;
return Container(
child: Card(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Detailpage(
snapshot.data.query.pages.values.toList()[index].title,
),
));
},
child: ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 8.0, vertical: 8.0),
leading: Container(
padding: EdgeInsets.only(right: 10.0),
decoration: new BoxDecoration(
border: new Border(
right: new BorderSide(
width: 1.5, color: Colors.grey)),
),
child: img == img.isEmpty
? SizedBox(height: 50.0,width: 50.0,child: Image.asset('images/ayurwiki.png'),)
:SizedBox(
height: 50.0,
width: 50.0,
child: FadeInImage.assetNetwork(
placeholder: 'images.png',
image: img,
fit: BoxFit.fill,
),
)
),
title: Text(
snapshot.data.query.pages.values.toList()[index].title),
),
)));
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
));
}
}
Herbslist.dart
class Herbslist {
String batchcomplete;
Continue herbslistContinue;
Limits limits;
Query query;
Herbslist({
this.batchcomplete,
this.herbslistContinue,
this.limits,
this.query,
});
factory Herbslist.fromJson(Map<String, dynamic> json) => Herbslist(
batchcomplete: json["batchcomplete"],
herbslistContinue: Continue.fromJson(json["continue"]),
limits: Limits.fromJson(json["limits"]),
query: Query.fromJson(json["query"]),
);
Map<String, dynamic> toJson() => {
"batchcomplete": batchcomplete,
"continue": herbslistContinue.toJson(),
"limits": limits.toJson(),
"query": query.toJson(),
};
}
class Continue {
String gcmcontinue;
String continueContinue;
Continue({
this.gcmcontinue,
this.continueContinue,
});
factory Continue.fromJson(Map<String, dynamic> json) => Continue(
gcmcontinue: json["gcmcontinue"],
continueContinue: json["continue"],
);
Map<String, dynamic> toJson() => {
"gcmcontinue": gcmcontinue,
"continue": continueContinue,
};
}
class Limits {
int categorymembers;
int pageimages;
Limits({
this.categorymembers,
this.pageimages,
});
factory Limits.fromJson(Map<String, dynamic> json) => Limits(
categorymembers: json["categorymembers"],
pageimages: json["pageimages"],
);
Map<String, dynamic> toJson() => {
"categorymembers": categorymembers,
"pageimages": pageimages,
};
}
class Query {
Map<String, Page> pages;
Query({
this.pages,
});
factory Query.fromJson(Map<String, dynamic> json) => Query(
pages: Map.from(json["pages"]).map((k, v) => MapEntry<String, Page>(k, Page.fromJson(v))),
);
Map<String, dynamic> toJson() => {
"pages": Map.from(pages).map((k, v) => MapEntry<String, dynamic>(k, v.toJson())),
};
}
class Page {
int pageid;
int ns;
String title;
Thumbnail thumbnail;
String pageimage;
Page({
this.pageid,
this.ns,
this.title,
this.thumbnail,
this.pageimage,
});
factory Page.fromJson(Map<String, dynamic> json) => Page(
pageid: json["pageid"],
ns: json["ns"],
title: json["title"],
thumbnail: json["thumbnail"] == null ? Thumbnail(source:'',width:0,height:0) : Thumbnail.fromJson(json["thumbnail"]),
pageimage: json["pageimage"] == null ? null : json["pageimage"],
);
Map<String, dynamic> toJson() => {
"pageid": pageid,
"ns": ns,
"title": title,
"thumbnail": thumbnail == null ? null : thumbnail.toJson(),
"pageimage": pageimage == null ? null : pageimage,
};
}
class Thumbnail {
String source;
int width;
int height;
Thumbnail({
this.source,
this.width,
this.height,
});
factory Thumbnail.fromJson(Map<String, dynamic> json) => Thumbnail(
source: json["source"],
width: json["width"],
height: json["height"],
);
Map<String, dynamic> toJson() => {
"source": source,
"width": width,
"height": height,
};
}
You are overriding the list you have. You need to append new data into your list.
Your logic should be like this:
// Declare a list
List<Herbslist> herbslist = [];
// Update the list
herblist.add(Herbslist.fromJson(json.decode(response.body)));
// Return the updated list
return herblist;
Without further information about your Herblist class, I can't guarantee that this would work. You probably need to flat you List of Lists.
Update
Your data structure seems unconvinient for this situation. There are better ways to structure your data and respresent it on the UI. You wrapped iterable data into a class and whenever you get new instance from that class you end up with having two different data stores. Since you pass the newest instance to the UI, only the latest results will show up on the list.
You should have a single data store (eg. a list or map for your API results) and append new data into it. You only have model classes now. They should declare how is your data structured. They shouldn't store any real data.