Flutter ListView pagination from API? - flutter

Pretty new to flutter. Trying to fit pagination loading from API into existing code but all the resources seem to point towards loading more from a static list.
Widget build(BuildContext context) {
return widget.placesList.isEmpty
? CircularLoadingWidget(height: 200)
: Container(
height: 200,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.placesList.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
},
child: PlaceCardWidget(place: widget.placesList.elementAt(index)),
);
},
),
);
}
I'm a little confused about streams and how to make an endless stream from a paginated API.
getCurrentLocation().then((LocationData _locationData) async {
final Stream<Place> stream = await getNearPlaces(_locationData, _locationData);
stream.listen((Place _place) {
setState(() => placesList.add(_place));
}, onError: (a) {
print(a);
}, onDone: () { print('Places: ' + placesList.length.toString()); });
});
Future<Stream<Place>> getNearPlaces(LocationData myLocation) async {
String _nearParams = '';
String _orderLimitParam = '';
if (myLocation != null) {
_orderLimitParam = 'orderBy=distance&perPage=10';
_nearParams =
'&myLon=${myLocation.longitude}&myLat=${myLocation.latitude}';
}
final String url = '${GlobalConfiguration().getString('api_base_url')}places/?$_nearParams&$_orderLimitParam';
final client = new http.Client();
final streamedRest = await client.send(http.Request('get', Uri.parse(url)));
return streamedRest.stream
.transform(utf8.decoder)
.transform(json.decoder)
.map((data) => data['results'])
.expand((data) => (data as List))
.map((data) {
return Place.fromJSON(data);
});
}
API format
{
"count": 119,
"next": "../places/?page=4",
"previous": "../places/?page=2",
"results": [],
}
Any guidance greatly appreciated.
Thanks!

Related

How to pagination with infinite_scroll_pagination flutter

List<Event> events = [];
int currentPage = 1;
Dio dio = Dio();
void getEvents() async {
try {
var response =
await Dio().get('http://52.90.175.175/api/events/get?page=$currentPage');
var data = response.data["data"]["data"] as List;
setState(() {
events = data.map((i) => Event.fromJson(i)).toList();
});
print(events);
} catch (e) {
print(e);
}
}
This is how I fetch my events and 10 events per page is loading and here is my json response of API
my full json
Next page URL and next page decide by API and I want to know how to pagination with infinite_scroll_pagination for my widget
ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
return ListTile(
contentPadding: const EdgeInsets.all(20),
title: Text(events[index].title),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(events[index].description),
Text("${events[index].start} - ${events[index].end}"),
],
),
);
},
),
no need to add any packages, add this code to initState
controller = new ScrollController()..addListener(_scrollListener);
and create this method.
_scrollListener() {
print(controller.position.extentAfter);
if (scrollController.position.maxScrollExtent == scrollController.offset) {
currentPage++;
getEvents();
}
}

How to display in dialog variable from API request in Flutter

I want to access the variable totalPresences that I have in my API request where I sum up the values from a map. Then I want to display the variable in my widget inside a dialog. How can I do that? Thanks in advance!
Here is my code
Future<List<Presence>> getPresencesByAthleteId() async {
try {
final response = await http.get(
Uri.parse();
if (response.statusCode == 200) {
Map map = json.decode(response.body);
List<Presence>? presencesList = [];
map.forEach((key, value) {
presencesList.add(Presence(
date: map.entries.first.key, count: map.entries.first.value));
var values = map.values;
var totalPresences = values.reduce((sum, element) => sum + element); //this I want to display it in a text
});
return presencesList.toList();
}
} catch (e) {
logger.e(e.toString());
}
return getPresencesByAthleteId(depId, teamId, id, context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Athlete>>(
...
secondary: IconButton(
icon: const Icon(Icons.history_outlined,
color: Colors.black, size: 25),
onPressed: () {
if (_athlete[i].currentMonthPresences! > 0) {
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
children: [
Column(
FutureBuilder<List<Presence>>(
future: getPresencesByAthleteId(_athlete[i].department!.id, widget._team.teamKey!.teamId, _athlete[i].id, context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
...
}),
);
} else if (snapshot.hasError) {
logger.e('${snapshot.error}');
}
}),
Container(
child:
Row(
children: [
const Text(''), // HERE I WANT TO DISPLAY totalPresences
)
],
),
),
It was easier than I thought I just needed a setState inside my api request like this:
int total=0;
Future<List<Presence>> getPresencesByAthleteId() async {
try {
final response = await http.get(
Uri.parse();
if (response.statusCode == 200) {
Map map = json.decode(response.body);
List<Presence>? presencesList = [];
map.forEach((key, value) {
presencesList.add(Presence(
date: map.entries.first.key, count: map.entries.first.value));
var values = map.values;
var totalPresences = values.reduce((sum, element) => sum + element);
setState(() {
totalPresences = total;
});
});
return presencesList.toList();
}
} catch (e) {
logger.e(e.toString());
}
return getPresencesByAthleteId(depId, teamId, id, context);
}
and then just display in dialog
.
.
const Text($total),

Infinite scroll with cubit and Firebase API

In my Flutter app the user can create new tasks and see them in the homepage, but right now I am fetching all the tasks from Firebase at once, and I wish I could do that using infinite scroll. I googled how to do this, but I really couldn't figure it out.
In my API project I have the following:
async getTasksByFilter(filters: Array<IFilter>): Promise<Array<ITask>> {
let tasksUser: Array<ITask> = [];
let collectionQuery: Query<DocumentData> = this.db.collection(
this.taskCollection,
);
let query = collectionQuery;
return new Promise((resolve, reject) => {
filters.forEach(entry => {
switch (entry.searchType) {
case 'where':
query = query.where(
entry.field,
entry.condition as WhereFilterOp,
entry.value,
);
break;
default:
break;
}
});
query
.orderBy('createdAt', 'asc')
.get()
.then(query => {
if (query.docs.length > 0) {
query.docs.forEach(doc => {
let task: ITask = this.transformDate(doc.data());
tasksUser.push(task);
});
}
return resolve(tasksUser);
})
.catch(error => {
return reject(error);
});
});
}
In my app I use this function to fetch the tasks
Future<List<Task>> getUserTasks(String _extension, Filter filters) async {
final Response response =
await client.post(Uri.parse("$BASE_URL$_extension"),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(filters.toJson()),
);
Iterable l = jsonDecode(response.body);
List<Task> tasks = List<Task>.from(l.map((model) => Task.fromJson(model)));
return tasks;
}
So when the tasks page is opened, the cubit changes its state to InitTaskListState, start to fetch all the tasks data and show a loading spinner for the user. When its done the state changes to LoadedTaskListState and the task list is displayed.
This is my code for it:
BlocConsumer<TaskListCubit, TaskListState>(
listenWhen: (previous, current) =>
current is FatalErrorTaskListState,
listener: (context, state) {
if (state is FatalErrorTaskListState) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(state.title ?? 'Error'),
content: Text(state.message ?? 'Error'),
actions: <Widget>[
TextButton(
child: const Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
builder: (context, state) {
if (state is InitTaskListState ||
state is LoadingTaskListState) {
return const ProgressView(message: 'Loading tasks');
}
if (state is LoadedTaskListState) {
final tasks = state.tasks;
return taskList(context, cubit, state, tasks);
}
return const Text('Unknown error');
},
)
TabBarView taskList(BuildContext context, TaskListCubit cubit,
LoadedTaskListState state, List<Task> tasks) {
return TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: List<Widget>.generate(
cubit.tabNames.length,
(int index) {
if (index == state.tabIndex) {
return Center(
child: tasks.isEmpty
? setEmptyListText(state.tabName)
: Column(
children: [
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
final task = tasks[index];
return TaskCard(
task,
state.tabName,
state.tabType,
cubit,
onClick: () {
push(
context,
TaskDetailsContainer(task, state.tabType),
);
},
);
},
itemCount: tasks.length,
),
),
],
),
);
} else {
return const Text('');
}
},
),
);
}
Does someone can explain me how to implement the infinite scroll in my project?
I think this will give you a pretty good idea of how to do it:
https://medium.com/flutter-community/flutter-infinite-list-tutorial-with-flutter-bloc-2fc7a272ec67
What you need to do is that each time reach the bottom of the list you will increment the limit of your current query, which can be easily done with firebase.
In your api call you can do like this:
firestore.collection("products").limit(_myLimit).get();
Make sure to update your limit as you reach the bottom of the screen and change states accordingly. Your bloc could keep track of the current limit and then you just increase it as you scroll down.

How to perform Pagination in Flutter

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

flutter pull up to refetch data from api

I want to use Refresh indicator so that when you pull up the page you are in right now rebuilds i will share with you my code i have tried many times but really i can't find a straight way around it here is my code
class Companies {
final int id;
final String name;
final String companyLogo;
Companies({this.id, this.name, this.companyLogo});
factory Companies.fromJson(Map<String, dynamic> json) {
return Companies(
id: json['id'],
name: json['name'],
companyLogo: json['company_logo'],
);
}
}
Future<List<Companies>> fetchCompanies() async {
final response = await http.get('$webSiteUrl/company/api/fetch');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return parseCompanies(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load the companies');
}
}
List<Companies> parseCompanies(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Companies>((json) => Companies.fromJson(json)).toList();
}
class CompaniesPage extends StatefulWidget{
#override
_CompaniesState createState() => _CompaniesState();
}
class _CompaniesState extends State<CompaniesPage> {
var refreshKey = GlobalKey<RefreshIndicatorState>();
Future<List<Companies>> companies;
#override
void initState() {
super.initState();
companies = fetchCompanies();
}
Future<Null> refreshCompanies() async {
refreshKey.currentState?.show(atTop: false);
setState(() {
companies = fetchCompanies();
});
return await companies;
}
Widget build(BuildContext context) {
checkVersion(context);
return Scaffold(
body: Center(
child: FutureBuilder<List<Companies>>(
future: companies,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Companies> companies = snapshot.data;
if(companies.length >= 1){
return MainLayout(
RefreshIndicator(
key: refreshKey,
onRefresh: refreshCompanies,
child: GridView.count(
crossAxisCount: 2 ,
children: List.generate(companies.length, (index) {
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Categories(companies[index].id, companies[index].name)),
)},
child: CompaniesInterface(companies[index].id , companies[index].name , companies[index].companyLogo),
);
}),
),
),
);
}else{
return EmptyDataBase();
}
} else if (snapshot.hasError) {
return ConnectionError();
}
// By default, show a loading spinner.
return DefaultTabController(
length: 1,
child: TabBar(
indicatorColor: Colors.transparent,
tabs: <Widget>[
Tab(
child: LoadingBouncingGrid.square(
backgroundColor: Colors.cyan,
size: 40,
),
),
],
),
);
},
),
),
);
}
}
as you can see i have tested it but it isn't refreshing the page correctly what i want is how should i rebuild this page on pull up so the missing part from my code i think is refreshCompanies() function
Update :
class _CompaniesState extends State<CompaniesPage> {
StreamController<List<Companies>> companiesStreamController;
var refreshKey = GlobalKey<RefreshIndicatorState>();
Future<List<Companies>> fetchCompanies() async {
final response = await http.get('$webSiteUrl/company/api/fetch');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return parseCompanies(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load the companies');
}
}
loadCompanies() async {
fetchCompanies().then((result) async {
companiesStreamController.add(result);
return result;
});
}
Future<Null> refreshCompanies() async {
refreshKey.currentState.show(atTop: true);
setState(() {
loadCompanies();
});
}
#override
void initState() {
checkVersion(context);
companiesStreamController = new StreamController();
Timer.periodic(Duration(seconds: 1), (_) => loadCompanies());
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<List<Companies>>(
stream: companiesStreamController.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Companies> companies = snapshot.data;
if(companies.length >= 1){
return MainLayout(
RefreshIndicator(
onRefresh: refreshCompanies,
key: refreshKey,
child: GridView.count(
crossAxisCount: 2 ,
children: List.generate(companies.length, (index) {
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Categories(companies[index].id, companies[index].name)),
)},
child: CompaniesInterface(companies[index].id , companies[index].name , companies[index].companyLogo),
);
}),
),
),
);
}else{......rest of code
Add a StreamController:
StreamController<List<Companies>> dataController;
Initialize it in your initState:
dataController = StreamController();
Move fetchCompanies inside your widget and before returning the result add it to your stream:
var result = parseCompanies(response.body);
dataController.add(result);
Use a StreamBuilder instead of FutureBuilder:
StreamBuilder<List<Companies>>(
stream: dataController.stream,
builder: (context, snapshot) {
...
}
)