I want to fetch paginated data in my flutter app from my website using REST API.
I have integrated pagination and now it is started working.
But the problem is that on loading more data, I am getting duplicate data instead of getting new data.
I think I am doing something wrong to increment the page no. in the _getAllNews() method
Here is my complete code, and I think I am doing very small mistake in this.
class Tedd extends StatefulWidget {
#override
_TeddState createState() => _TeddState();
}
class _TeddState extends State<Tedd> {
List<NewsModel> _newsList = [];
bool isLoading = true;
int currentPage = 1;
bool hasReachedEnd = false;
ScrollController scrollController = ScrollController();
_getAllNews(page) async {
setState(() {
isLoading = true;
});
var articles = await http.get(Uri.parse(
"https://pkbhai.com/myprojects/kids-stories/api/all-stories?page=${page}"));
var result = json.decode(articles.body);
print(result);
result['data'].forEach((data) {
var news = NewsModel();
news.id = data["id"];
news.articleTitle = data["name"];
if (mounted) {
setState(() {
_newsList.add(news);
isLoading = false;
currentPage = currentPage++;
});
}
});
}
void handleNext() {
scrollController.addListener(() async {
if (scrollController.position.maxScrollExtent ==
scrollController.position.pixels) {
_getAllNews(currentPage);
}
});
}
#override
void initState() {
_getAllNews(currentPage);
handleNext();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: scrollController,
itemCount: _newsList.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == _newsList.length) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container(
height: 150, child: Text(_newsList[index].articleTitle!));
},
),
);
}
}
What am I doing wrong?
i have check the api, now the problem of the repeated data is coming from the api url.
after check the api and test base on this 3 parameter,
current_page= 1,
from=1,
last_page=3,
but is not working...
solution:
contact the developer of the api and check the api or recreate another working pagination url for you to make request
Related
In home page/screen, i have 3 sections :
categories list view
popular food list view
new food list view
I want to load all api data whenever home page/screen load, i created methods for categories and popular food section.
But there is a problem only one funtion call in init method and one section is load but when i change any thing and save then next section load,
So what is the best way to call this api and load is UI properly.
here is my code.
Home Controller
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter_admin/consts/consts.dart';
import '../api_service/api_endpoints.dart';
import '../model/category_model.dart';
import '../model/popular_recipe_model.dart';
class HomeController extends GetxController {
var currentNavIndex = 0.obs;
var isLoading = false.obs;
//category list variable
List categoryList = [].obs;
//popular recipe list variable
List popularRecipeList = [].obs;
#override
void onInit() {
fetchCategory();
fetchPopularRecipe();
super.onInit();
}
//change navigation tab index
changeIndex(index) {
currentNavIndex.value = index;
}
// fetch category
Future<List?> fetchCategory() async {
var client = http.Client();
isLoading.value = true;
try {
var response = await client.get(Uri.parse(
ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.fetchCat));
if (response.statusCode == 200) {
var jsonString = jsonDecode(response.body);
var data = jsonString['data'];
categoryList =
List.from(data).map((e) => Categories.fromJson(e)).toList();
isLoading.value = false;
return categoryList;
} else {
Get.snackbar("Error", "data not found");
return null;
}
} finally {
client.close();
}
}
// fetch popular recipe
Future<List?> fetchPopularRecipe() async {
var client = http.Client();
isLoading.value = true;
try {
var response = await client.get(Uri.parse(
ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.popularRecipe));
if (response.statusCode == 200) {
var jsonString = jsonDecode(response.body);
var data = jsonString['data'];
popularRecipeList =
List.from(data).map((e) => PopularRecipe.fromJson(e)).toList();
isLoading.value = false;
return popularRecipeList;
} else {
Get.snackbar("Error", "data not found");
return null;
}
} finally {
client.close();
}
}
}
Home Screen
// ),
Obx(()=>SizedBox(
height: Dimensions.height300,
child: controller.isLoading.value ? const Center(child: CircularProgressIndicator()) : ListView.builder(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: controller.popularRecipeList.length,
itemBuilder: (context, index) {
return popularRecipe(
title: "${controller.popularRecipeList[index].name}",
time: "${controller.popularRecipeList[index].recipeTime}",
icon: const Icon(Icons.bookmark_outline_sharp),
img: "${controller.popularRecipeList[index].imageUrl}");
}),
),
),
Because isLoading is set to false as soon as one of the Future completes. Use different flag for them or if you want to use a single flag then call a new Function in init method and wait in that function for the futures to complete.
Future<void> loadData() async {
isLoading.value = true;
await fetchCategory();
await fetchPopularRecipe();
isLoading.value = false;
}
Also, you don't need to return the value because you are already assigning in the function.
I am trying to make a Telegram client for android using the tdlib flutter port. I am currently attempting to make a contact list of sorts, by requesting it from telegram and making a listview of textbuttons.
The only issue is that since the library is async, I get the contact list after the layout has been initialized. Is it possible to somehow rebuild the layout or update it to make the list load properly.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fima/services/telegram_service.dart';
import 'package:tdlib/td_api.dart' show TdError;
import 'package:provider/provider.dart';
import 'package:tdlib/td_api.dart' as TdApi;
class ContactListScreen extends StatefulWidget {
#override
_ContactListScreenState createState() => _ContactListScreenState();
}
class _ContactListScreenState extends State<ContactListScreen> {
final String title = 'Contact list';
bool _loadingStep = false;
String _Error;
String route = "initRoute";
List<TextButton> contacts = [];
#override
void initState() {
super.initState();
_getContacts(onError: _handelError,);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Color(0xD3232323),
),
body: Container(
child:
ListView (
children: contacts,
),
),
);
}
Future _getContacts(
{
void Function(TdError) onError,
}) async {
final result = await context.read<TelegramService>().send(
TdApi.GetContacts(
),
);
if (result is TdError && onError != null) {
onError(result);
}
TdApi.Users users = result;
for (var i = 0; i < users.totalCount; i++) {
final result = await context.read<TelegramService>().send(
TdApi.GetUser(userId: users.userIds[i]),
);
TdApi.User user = result;
print(user.firstName + " " + user.lastName);
final contact = TextButton(
onPressed: () {
print("Test");
},
child: Text(user.firstName + " " + user.lastName),
);
setState(() {
contacts.add(contact);
});
}
}
void _handelError(TdError error) async {
setState(() {
_loadingStep = false;
_Error = error.message;
});
}
}
I have attempted to use setState, but without much success, could anyone be so kind as to provide me with the solution to this problem?
Using the FutureBuilder might help. It is a widget that builds itself based on the latest snapshot of interaction with a Future.
You can modify your build to return a FutureBuilder something like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getContacts,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
//Use snapshot data to build by returning your Container with List
}
else{
//Return a CircularProgressIndicator
}
}
}
Refer the documentation on the FutureBuilder class here.
I want to fetch all the infinite list view data in parts, means Initially it load 10 data item and on scroll more it should fetch next 10 items.
I am fetching data from my Laravel api and in my Laravel api endpoint there is not option for per_page, so Please help me to integrate load more option of list view data.
Here is my List data.
List<Category> _categoryList = List<Category>();
CategoryService _categoryService = CategoryService();
bool isLoading = true;
#override
void initState() {
super.initState();
_getAllCategories();
}
_getAllCategories() async {
var categories = await _categoryService.getCategories();
var result = json.decode(categories.body);
result['data'].forEach((data) {
var model = Category();
model.id = data["id"];
model.name = data["categoryName"];
model.icon = data["categoryIcon"];
setState(() {
_categoryList.add(model);
isLoading = false;
});
});
}
And I am fetching all data in simple ListTile.
child: ListView.builder(
itemCount: //_categoryList.length,
itemBuilder: (context, index) {
return ListTile(
title: _categoryList.name
);
},
),
So I have a trick and I am using it in my project. What we need here is to load more data when we are at the end of the list. So to do that we can use the ListView.builder() only:
child: ListView.builder(
itemCount: _categoryList.length + 1,
itemBuilder: (context, index) {
if(index == _categoryList.length){
// loadMore();
// return Loading();
}
return ListTile(
title: _categoryList.name
);
}),
So what we are doing is that we have set _categoryList.length + 1 to the itemCount. If _categoryList.length was 10 then we will have 11 items in the ListView and the index range will be 0 - 10. So inside the builder, we are checking that the index is equals to _categoryList.length which is 10. If the index is equals to the length of _categoryList.length, then we are at the end of the List and we can simply call some functions to load more data from Api or to show a loading widget.
I guess I got your question right, in this way you can simply lazy load data when user gets to the end of the List without using any third party libraries.
This process called pagination. Laravel provides a function for it check Pagination in Laravel
For example
$users = DB::table('users')->paginate(15);// 15 is limit per page
And check it too Paging lib in flutter dev.
I have created a sample to load more data using web service
You can see this example and you can implement for you json data.
https://android-pratap.blogspot.com/2018/12/flutter-infinite-listview-using.html
import 'package:akeepo/randomuser_infinitelist.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: InfiniteUsersList(),
);
}
}
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class InfiniteUsersList extends StatefulWidget {
static String tag = 'users-page';
#override
State<StatefulWidget> createState() {
return new _InfiniteUsersListState();
}
}
class _InfiniteUsersListState extends State<InfiniteUsersList> {
List<User> users = new List<User>();
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
int pageNumber = 0;
#override
void initState() {
super.initState();
// Loading initial data or first request to get the data
_getMoreData();
// Loading data after scroll reaches end of the list
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
// to show progressbar while loading data in background
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// Webservice request to load 20 users data using paging
Future<List<User>> _getUsers() async {
List<User> users = new List<User>();
setState(() {
pageNumber++;
});
String url =
"https://api.randomuser.me/?page=$pageNumber&results=20&seed=abc";
print(url);
var response = await http.get(url);
var jsonData = json.decode(response.body);
print(jsonData);
var usersData = jsonData["results"];
for (var user in usersData) {
User newUser = User(user["name"]["first"] + user["name"]["last"],
user["email"], user["picture"]["large"], user["phone"]);
users.add(newUser);
}
return users;
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() {
isPerformingRequest = true;
});
List<User> newEntries = await _getUsers(); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
users.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Users',
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
body: Container(
child: ListView.builder(
shrinkWrap: true,
controller: _scrollController,
itemCount: users.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == users.length) {
return _buildProgressIndicator();
} else {
return ListTile(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
UserDetailPage(users[index])));
},
title: Text(users[index].fullName),
subtitle: Text(users[index].mobileNumber),
leading: CircleAvatar(
backgroundImage: NetworkImage(users[index].imageUrl)),
);
}
})),
);
}
}
class User {
final String fullName;
final String email;
final String imageUrl;
final String mobileNumber;
User(this.fullName, this.email, this.imageUrl, this.mobileNumber);
}
// User Detail Page
class UserDetailPage extends StatelessWidget {
final User user;
UserDetailPage(this.user);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("User Details"),
),
body: Center(
child: Text(
user.fullName,
style: TextStyle(fontSize: 35.0),
),
),
);
}
}
I have a function that is supposed to fetch me a list of Restaurants objects from firestore based on location.
the function does its job perfectly when i first run the app but after using the app from another device and updating resturants data in firestore documents, i somehow get duplicates of the restaurants list items.
here is the code for the function that fetch the the restaurants objects list:
Future<void> fetchRestaurantsList() async {
try {
Position position = await Geolocator().getCurrentPosition(
desiredAccuracy:
Platform.isIOS ? LocationAccuracy.lowest : LocationAccuracy.high);
final dbRestaurant = firestore
.collection('testing')
.document('users')
.collection('restaurant');
geo.collection(collectionRef: dbRestaurant)
.within(
center: GeoFirePoint(
position.latitude,
position.longitude
),
radius: 45.0,
field: 'resturantLocation')
.listen((event) {
restaurantList.clear();
await event.forEach((element){
final distance = Distance.getDistanceFromLatLonInKm( // calculating distance for each restaurant
position.latitude,
position.longitude,
element.data['location']['geopoint'].latitude,
element.data['location']['geopoint'].longitude)
restaurantList.add(Restaurant(
id: element.documentID,
logo: element.data['logo'],
name: element.data['name'],
distance: distance ,
));
notifyListeners();
});
});
} catch (e) {
print(e.toString());
}
} finally {
notifyListeners();
}
}
and this is the page that contains the list: (its under a parent widget which contains other tabs)
class RestruntsListTab extends StatefulWidget {
final MainModel model;
RestruntsListTab({#required this.model});
#override
State<StatefulWidget> createState() {
return _RestruntsListTabState();
}
}
class _RestruntsListTabState extends State<RestruntsListTab>
#override
void initState() {
widget.model.fetchRestaurantsList();
widget.model.checkLocationService().then((isActive) {
if (isActive) {
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
language.enableLcation,
style: TextStyle(
fontFamily: 'eff', fontSize: 18, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.grey,
));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount:model.restaurantList.length,
itemBuilder: (context,index) {
return Row(
children: <Widget>[
Text(model.restaurantList[index].name),
Text(model.restaurantList[index].distance),
],
)
}
);
})
}
}
this is a simplified code for demonstration but the actual code is pretty similar.
if you have encountered similar issues kindly share your experience.
thank you all.
check that fetchRestaurantsList() method is not called on widget build
or it is in StreamBuilder method...it's because .listen((event) { this method it is like a stream so you have to use flag like bool variable to run the code inside it
if(mybool==false){// the other code goes.... setStste({mybool=true;})}
in this way it only excute the code once
There might be something wrong with the code, but I don't see it. What you can try doing is wrapping the content of forEach with
if(restaurantList.where((item) => item.id == element.documentID).isEmpty){
}
That should filter out duplicates.
Have looked at similar questions, can't see any common mistakes. Once the factory seems to create the object with no issues. However, calling any of the methods generates a NoSuchMethodError. Been debugging for days, out of ideas. Have similar code using data models of that general layout with no issues.
This is the code for the data model
class Performer {
String avatar, header, name, username;
int id, subscribePrice;
bool isRealPerformer,
isPerformer,
hasStories,
hasStream,
isPaywallRestriction;
Performer(
{this.avatar,
this.header,
this.name,
this.username,
this.id,
this.subscribePrice,
this.isRealPerformer,
this.isPerformer,
this.hasStories,
this.hasStream,
this.isPaywallRestriction});
factory Performer.fromJson(Map<String, dynamic> performer) {
return Performer(
avatar: performer["avatar"],
header: performer["header"],
name: performer["name"],
username: performer["username"],
id: performer["id"],
subscribePrice: performer["subscribePrice"],
isRealPerformer: performer["isRealPerformer"],
isPerformer: performer["isPerformer"],
hasStories: performer["hasStories"],
hasStream: performer["hasStream"],
isPaywallRestriction: performer["isPaywallRestriction"]);
}
}
This is the code that populates the models
Future<List<Performer>> getSubscriptions() async {
List<Performer> performers = [];
String url = "some API url";
String res = await _callServer(url);
if (res.isNotEmpty) {
List<dynamic> payload = json.decode(res);
payload.forEach((element) {
performers.add(new Performer.fromJson(element));
});
return performers;
} else return performers;
}
Future<Performer> getPerformer(int performerID) async {
List<Performer> subs = await getSubscriptions();
Performer performer;
int prefIndex;
for (int x = 0; x < subs.length; x++) {
if (subs[x].id == performerID){
performer = subs[x];
break;
}
}
if (performer.avatar != null) {
print("found ${performer.username}");
return performer;
} else return null;
}
This is the code that generates the UI element based on the model
class ProfilePic extends StatefulWidget {
final int id;
ProfilePic({Key key, #required this.id}) : super();
#override
State<StatefulWidget> createState() => _profilePicState();
}
class _profilePicState extends State<ProfilePic> {
Performer performer;
#override
void initState() {
// TODO: implement initState
super.initState();
Backend().getPerformer(widget.id).then((value) {
performer = value;
setState(() {});
});
}
#override
Widget build(BuildContext context) {
print("profile for: ${widget.id}");
return Container(
child: performer == null ? Container() : CircleAvatar(
radius: 30.0,
backgroundImage:
NetworkImage(performer.avatar),
backgroundColor: Colors.transparent,
),
);
}
In your getSubscriptions, try
payload.forEach((element) {
performers.add(new Performer.fromJson(Map.from(element)));
});
Nothing wrong with the code was returning the post ID not the User ID to the function, hence the null error.