I am trying to build a list from http server, and would like to implement page load for large data set. I tried with lazy_load_scrollview package and follow the example, I can load additional records from the server on End of page, but when new records are loaded, the list jumps up to the first record. How could I avoid this?
int totalRecords = 0;
int recordsPerPage = 5;
int currentPage = 1;
int totalPages = 1;
List<dataRecord> dataList = [];
List<dataRecord> fullList = [];
bool isLoading = false;
//class definitions...
class searchClient extends StatefulWidget {
#override
_searchClientState createState() => _searchClientState();
}
class _searchClientState extends State<searchClient> {
final _searchItemController = TextEditingController();
#override
void initState() {
super.initState();
dataList = [];
currentPage = 1;
}
List<dataRecord> parseJson(String responseBody) {
final parsed =
convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<dataRecord>((json) => dataRecord.fromJson(json)).toList();
}
#override
void dispose() {
_searchItemController.dispose();
super.dispose();
}
void loadData() async {
setState(() {
isLoading = true;
});
if (totalRecords == 0) {
final response2 = await http.get(
Uri.parse('http://192.168.0.8:88/searchcustomer_gettotal' +
'?userID=' +
globals.userID.toString() +
'&token=' +
globals.token +
'&name=' +
_searchItemController.text),
headers: {
"Content-type": "application/json",
"Accept": "application/json",
"Connection": "Keep-Alive",
},
);
Map<String, dynamic> totalMap = convert.jsonDecode(response2.body);
var total = totals.fromJson(totalMap);
totalRecords = total.total;
var t = totalRecords / recordsPerPage;
if (totalRecords < recordsPerPage) {
totalPages = 1;
}else if (totalRecords%recordsPerPage == 0) {
totalPages = t.round();
}else{
var t = totalRecords / recordsPerPage;
totalPages = t.round()+ 1;
}
print(totalRecords);
}
;
final response = await http.get(
Uri.parse('http://192.168.0.8:88/searchcustomer' +
'?userID=' +
globals.userID.toString() +
'&token=' +
globals.token +
'&name=' +
_searchItemController.text +
'&pageno=' +
currentPage.toString() +
'&perpage=' +
recordsPerPage.toString()),
headers: {
"Content-type": "application/json",
"Accept": "application/json",
"Connection": "Keep-Alive",
},
);
setState(() {
isLoading = false;
});
dataList = parseJson(response.body);
fullList = List.from(fullList)..addAll(dataList);
}
void loadMore() {
if (currentPage < totalPages) {
currentPage += 1;
loadData();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Search Client')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 20),
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), labelText: 'Search Item'),
controller: _searchItemController,
),
),
Container(
height: 45,
width: 250,
decoration: BoxDecoration(
color: Colors.teal, borderRadius: BorderRadius.circular(16)),
child: TextButton(
onPressed: () {
totalRecords = 0;
fullList = [];
loadData();
},
child: Text(
'Search',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
SizedBox(height: 20),
isLoading
? Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: CircularProgressIndicator(),
),
)
: Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
child: LazyLoadScrollView(
isLoading: isLoading,
onEndOfPage: () {
loadMore();
},
child: ListView.builder(
itemCount: fullList.length,
itemBuilder: (BuildContext context, int index) {
return dataCard(context, fullList, index);
}),
),
),
),
],
),
);
}
}
Could you try removing this widget of your tree
Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: CircularProgressIndicator(),
),
)
in order to only have your Scrollview ?
My guess is that since you're calling the setState method at load time, your whole widget tree is rebuilt, and when your new datas are fetched the whole listview is rebuilt, losing the previous position.
I'll edit my answer with some code if this doesn't fix your problem
Related
import 'dart:convert';
import 'package:cmail/globals.dart' as globals;
import 'package:cmail/Background/seeMailBackground.dart';
import 'package:cmail/UIs/floatingactingbutton.dart';
import 'package:cmail/fontss/MyFlutterApp.dart';
import 'package:cmail/DrawerClass.dart';
import 'package:cmail/UIs/inboxUI.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import '../UIs/shimmirLoading.dart';
class mailList extends StatefulWidget {
int employeeId;
//String username;
mailList({Key? key, required this.employeeId}) : super(key: key);
#override
State<mailList> createState() => _mailListState();
}
class _mailListState extends State<mailList> {
late List<dynamic> mails;
TextEditingController subjectController = TextEditingController();
// final scrollController=ScrollController();
// late int start;
// late int limit;
bool flag1 = false;
bool flag2 = false;
bool icon1 = false;
bool icon2 = false;
int x = 4;
#override
void initState() {
super.initState();
flag1 = false;
flag2 = false;
icon1 = false;
icon2 = false;
}
Future<List<dynamic>> showList(int buttonPressed, String subject) async {
int sortBy = 1, sortType = 1;
if (buttonPressed == 0) {
if (flag1 == false) {
sortBy = 1;
sortType = 0;
flag1 = true;
} else {
sortBy = 1;
sortType = 1;
flag1 = false;
}
}
if (buttonPressed == 1) {
if (flag2 == false) {
sortBy = 0;
sortType = 0;
flag2 = true;
} else {
sortBy = 0;
sortType = 1;
flag2 = false;
}
}
try {
final String currentTimeZone =
await FlutterNativeTimezone.getLocalTimezone();
var response = await http.post(
Uri.parse(
"https://www.myciright.com/Ciright/api/cirightMailApp/m1351188"),
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Charset': 'utf-8',
'Authorization':
'Basic bWVodWwua0BjaXJpZ2h0LmNvbTpDaXJpZ2h0QDEyMw=='
},
body: jsonEncode({
"employeeId": widget.employeeId,
"start": 0,
"limit": 1000,
"verticalId": 18,
"subject": subject,
"sortBy": sortBy,
"sortType": sortType,
"timeZone": currentTimeZone
}));
var data = jsonDecode(response.body.toString());
if (response.statusCode == 200) {
mails = await data['data']['mailList'];
print(mails);
print('List successful');
return Future.value(mails);
} else {
print('failed');
return [];
}
} catch (e) {
print(e.toString());
return [];
}
}
#override
Widget build(BuildContext context) {
return seeMailBackground(
background: globals.background,
child: floatingactionbutton(
employeeid: widget.employeeId,
child: Scaffold(
drawer: DrawerClass(lemployeeId: widget.employeeId),
appBar: AppBar(
title: Text(
'Inbox',
style: TextStyle(color: Colors.black),
),
iconTheme: IconThemeData(
color: Colors.black, //change your color here
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
setState(() {
x = 1;
icon1 = !icon1;
});
},
icon: icon1
? Icon(
MyFlutterApp.sort_name_down,
color: Colors.black,
size: 20,
)
: Icon(
MyFlutterApp.sort_name_up,
color: Colors.black,
size: 20,
)),
IconButton(
onPressed: () {
setState(() {
x = 0;
icon2 = !icon2;
});
},
icon: icon2
? Icon(MyFlutterApp.sort_number_down,
color: Colors.black, size: 20)
: Icon(MyFlutterApp.sort_number_up,
color: Colors.black, size: 20)),
],
),
SizedBox(
width: 25,
)
],
backgroundColor: Colors.white,
elevation: 3,
),
body: Column(
children: [
SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: TextField(
cursorColor: Colors.grey,
controller: subjectController,
onSubmitted: (value) {
setState(() {
showList(x, subjectController.text);
});
},
decoration: InputDecoration(
contentPadding:
EdgeInsets.symmetric(vertical: 1, horizontal: 20),
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Colors.greenAccent),
),
hintText: 'Search',
hintStyle: TextStyle(color: Colors.grey, fontSize: 18),
),
),
),
Expanded(
child: RefreshIndicator(
onRefresh: () {
return Future.delayed(
Duration(seconds: 1),
() {
setState(() {
showList(4, "");
});
},
);
},
child: FutureBuilder<List<dynamic>>(
future: showList(x, subjectController.text),
builder: (ctx, snapshot) {
List<dynamic>? mail = snapshot.data;
switch (snapshot.connectionState) {
case ConnectionState.done:
if(mail!.isEmpty){
return Center(child: Text("No Inbox Mails...",style: TextStyle(color: Colors.grey,fontSize: 20),),);
}
return _buildListView(mail!);
default:
return _buildShimmerScreen();
}
},
),
),
),
],
),
),
),
);
}
Widget _buildListView(List<dynamic> contacts) {
return ListView.builder(
itemBuilder: (ctx, idx) {
return inboxUI(
SenderSubject: contacts[idx]['subject'],
senderName: contacts[idx]['employeeName'],
date: contacts[idx]['createdDate'].toString(),
empid: contacts[idx]['employeeId'],
isStarred: contacts[idx]['isStarred'],
mailId: contacts[idx]['mailId'],
starMailId: contacts[idx]['starMailId'],
loginempid: widget.employeeId,
isAttachment: contacts[idx]['isAttachment'],
subject: contacts[idx]['subject'],
mailContent: contacts[idx]['mailContent'],
isRead: contacts[idx]['isRead']);
},
itemCount: contacts.length,
);
}
Widget _buildShimmerScreen() {
return ListView.builder(
itemBuilder: (ctx, idx) {
return shimmirLoading();
},
itemCount: 9,
);
}
// Widget _buildLoadingScreen() {
// return Center(
// child: Container(
// width: 50,
// height: 50,
// child: CircularProgressIndicator(),
// ),
// );
// }
}
I want to paginate for 10 mails at a time and change the variable start and limit by 10 when the user reaches the limit of seeing 10 mails. I tried using the scroll controller in listview builder but i am not able to achieve what is expected. Please help me out and thank you in advance for any inputs.
Faced a problem. I started to paginate the list so that 10 elements are displayed, when I reach the bottom of the list using controller.position.extentAfter < 30 I check how far we have gone down and if at the very bottom I change the value of isLoadMoreRunning and show CircularProgressIndicator. I will also add +10 elements to the limit variable for each call to display. I seem to have done everything right, but pagination does not work for me, it shows the first 10 elements, and then nothing passes, new elements are not loaded when I scrolled to the very bottom. What could be the problem?
home
late ScrollController controller;
bool isFirstLoadRunning = false;
bool isLoadMoreRunning = false;
bool hasNextStation = true;
int limit = 10;
void _firstLoadStations() async {
final StationCubit stationCubit = BlocProvider.of<StationCubit>(context);
setState(() {
isFirstLoadRunning = true;
});
try {
stationsList =
await stationCubit.getAllPublicChargingStations(limit: limit);
} catch (error) {
print(error);
}
setState(() {
isFirstLoadRunning = false;
});
}
void _loadMoreStations() async {
final StationCubit stationCubit = BlocProvider.of<StationCubit>(context);
if (hasNextStation == true &&
isFirstLoadRunning == false &&
controller.position.extentAfter < 30) {
setState(() {
isLoadMoreRunning = true;
});
limit += 10;
try {
var fetchedStations =
await stationCubit.getAllPublicChargingStations(limit: limit);
if (fetchedStations.isNotEmpty) {
setState(() {
stationsList.addAll(fetchedStations);
});
} else {
setState(() {
hasNextStation = false;
});
}
} catch (error) {
print(error);
}
setState(() {
isLoadMoreRunning = false;
});
}
// _foundAddressesInStationList = stationsList;
}
#override
void initState() {
_firstLoadStations();
controller = ScrollController()
..addListener(() {
_loadMoreStations();
});
_foundAddresses = _allAddresses;
_foundStation = _allStation;
super.initState();
}
#override
void dispose() {
controller.removeListener(() {
_loadMoreStations();
});
super.dispose();
}
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
final double paddingTop = MediaQuery.of(context).padding.top;
final StationCubit stationCubit = BlocProvider.of<StationCubit>(context);
return Container(
width: size.width,
height: size.height,
child: _child(size, paddingTop, stationCubit),
);
}
Widget _child(Size size, double paddingTop, StationCubit stationCubit) =>
BlocBuilder<StationCubit, StationState>(
builder: (context, state) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
children: [
const SizedBox(height: 17),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
_addresses(size, stationCubit),
],
),
),
),
],
),
),
);
Widget _addresses(Size size, StationCubit stationCubit) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 2,
),
child: SizedBox(
width: size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isFirstLoadRunning
? const CircularProgressIndicator(
color: Colors.white)
: const Text(
'Addresses',
style: constants.Styles.smallBookTextStyleWhite,
),
const SizedBox(height: 25),
ListViewSearch(
stationList: stationsList,
controller: controller,
),
const SizedBox(height: 20),
if (isLoadMoreRunning == true)
const Padding(
padding: EdgeInsets.only(top: 10, bottom: 40),
child: Center(
child: CircularProgressIndicator(),
),
),
if (hasNextStation == false)
Container(
padding: const EdgeInsets.only(top: 30, bottom: 40),
color: Colors.amber,
child: const Center(
child:
Text('You have fetched all of the content'),
),
),
],
),
)),
),
),
),
);
}
cubit
Future<List<PublicChargingStationModel>> getAllPublicChargingStations(
{required int limit}) async {
var result =
await StationRepository().getAllPublicChargingStations(limit: limit);
return result;
}
repository
Future<List<PublicChargingStationModel>> getAllPublicChargingStations(
{int? limit}) async {
try {
var apiKeyMap = await ApiKey.getCryptoApiKeyMap();
apiKeyMap!.addAll({'limit': limit.toString()});
final Uri url = Uri.parse(
'${ApiConfig.schema}://${ApiConfig.domain}/${ApiConfig.uriPrefix}/stations',
).replace(queryParameters: apiKeyMap);
final response = await http.get(url);
if (response.statusCode == 200) {
final data = jsonDecode(response.body)['data'] as List;
return data
.map((json) => PublicChargingStationModel.fromJson(json))
.toList();
}
return List<PublicChargingStationModel>.empty();
} catch (_) {
print(_);
return List<PublicChargingStationModel>.empty();
}
}
you need set SingleChildScrollView parameter controller: controller.
I filter the one litview builder list to another checkbox list and this is working, but when i add this CheckboxListTile in Alert dialog when no filter reflection showing.
I used StatefulBuilder also but is not useful to filtering listview.
[This is first link to show Product list data][1]
This is second link of filter list
I am stuck in how to one data filter to another item filter that availiable in Alert dialog.
This is Api link
const String baseUrl = "https://run.mocky.io/v3/";
const String productDataUrl = baseUrl + "4ecbd2ea-a725-438b-b8fc-da8fc08bc875";
const String productCategoryDataUrl =
baseUrl + "0595387e-732e-47cf-9675-244fed9fc014";
This is product model class that listview model that we want to filter
class ProductDataModel {
int? productId;
String? productName;
int? productPrice;
int? productKG;
String? productCategoryId;
ProductDataModel(
{this.productId,
this.productName,
this.productPrice,
this.productKG,
this.productCategoryId});
ProductDataModel.fromJson(Map<String, dynamic> json) {
productId = json['ProductId'];
productName = json['ProductName'];
productPrice = json['ProductPrice'];
productKG = json['ProductKG'];
productCategoryId = json['ProductCategoryId'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProductId'] = this.productId;
data['ProductName'] = this.productName;
data['ProductPrice'] = this.productPrice;
data['ProductKG'] = this.productKG;
data['ProductCategoryId'] = this.productCategoryId;
return data;
}
}
This ProductCategory Model class for using filter and this model class
data in CheckboxTile
class ProductCategoryDataModel {
int? productCategoryId;
String? productCategoryName;
bool? isChecked=false;
ProductCategoryDataModel(
{this.productCategoryId, this.productCategoryName, this.isChecked});
ProductCategoryDataModel.fromJson(Map<String, dynamic> json) {
productCategoryId = json['ProductCategoryId'];
productCategoryName = json['ProductCategoryName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ProductCategoryId'] = this.productCategoryId;
data['ProductCategoryName'] = this.productCategoryName;
return data;
}
}
This is code of showing listview and filter data using
checkboxlisttile that data in Alert dialog .
import 'dart:convert';
import 'dart:developer';
import 'package:dummy_checkbox_filter_list/commons/api_url.dart';
import 'package:dummy_checkbox_filter_list/model/product_category_data_model.dart';
import 'package:dummy_checkbox_filter_list/model/product_data_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<ProductDataModel> productDataList = [];
Set<ProductDataModel> productDataListDisplay = {};
List<ProductCategoryDataModel> productCategoryDataList = [];
List<ProductCategoryDataModel> selectProductCategoryDataList = [];
ScrollController scrollController = ScrollController();
bool isShowLoader = false;
getProductList() async {
try {
setState(() {
isShowLoader = true;
});
final response = await http.get(Uri.parse(productDataUrl));
log("Response URL=> ${response.toString()}");
if (response.statusCode == 200) {
var decode = jsonDecode(response.body);
log("Response Body=> ${decode.toString()}");
for (int i = 0; i < decode.length; i++) {
productDataList.add(ProductDataModel.fromJson(decode[i]));
}
setState(() {
isShowLoader = false;
});
return productDataList;
} else {
setState(() {
isShowLoader = false;
});
throw "Unable to retrieve product data.";
}
} catch (e) {
setState(() {
isShowLoader = false;
});
print('Something went wrong.');
}
}
getProductCategoryList() async {
try {
setState(() {
isShowLoader = true;
});
final response = await http.get(Uri.parse(productCategoryDataUrl));
log("Response URL=> ${response.toString()}");
if (response.statusCode == 200) {
var decode = jsonDecode(response.body);
log("Response Body=>${decode.toString()}");
for (int i = 0; i < decode.length; i++) {
productCategoryDataList
.add(new ProductCategoryDataModel.fromJson(decode[i]));
}
setState(() {
isShowLoader = false;
});
return productCategoryDataList;
} else {
setState(() {
isShowLoader = false;
});
throw "Unable to retrieve product data.";
}
} catch (e) {
setState(() {
isShowLoader = false;
});
print('Something went wrong.');
}
}
#override
void initState() {
getProductList();
getProductCategoryList();
super.initState();
}
#override
Widget build(BuildContext context) {
filterProduct(productDataList);
return Scaffold(
body: SafeArea(
child: isShowLoader == false
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
titlePadding: EdgeInsets.zero,
backgroundColor: Color(0xFF242424),
title: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(
Icons.close,
color: Colors.white,
size: 25,
))
],
),
contentPadding: EdgeInsets.zero,
content: Container(
padding: EdgeInsets.all(5),
width: double.maxFinite,
child: ListView(
padding: EdgeInsets.all(8.0),
children: [
Text(
"dfff "+productCategoryDataList[0].isChecked.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 16),
),
...productCategoryDataList
.map((e) => CheckboxListTile(
controlAffinity:
ListTileControlAffinity
.leading,
title: Text(
"${e.productCategoryName}",
style: TextStyle(
color: Colors.white,
fontSize: 16),
),
value: e.isChecked,
selected:
selectProductCategoryDataList
.contains(e),
onChanged: (val) {
print("val: "+val.toString());
setState(() {
e.isChecked = val;
selectProductCategoryDataList
.contains(e)
? selectProductCategoryDataList
.remove(e)
: selectProductCategoryDataList
.add(e);
print(
"_isChecked: ${e.isChecked}");
});
},
))
],
),
),
);
},
);
});
},
style: ElevatedButton.styleFrom(
primary: Colors.black,
onPrimary: Colors.white,
minimumSize: Size(
MediaQuery.of(context).size.width * 0.30,
MediaQuery.of(context).size.height * 0.05)),
child: Text(
"Filter",
style: TextStyle(fontSize: 15),
),
),
Container(
child: ListView.builder(
itemCount: productDataListDisplay.length,
shrinkWrap: true,
controller: scrollController,
itemBuilder: (context, index) {
return Card(
child: Container(
padding: EdgeInsets.only(top: 15, bottom: 15),
color: (index % 2 == 0)
? Colors.grey.shade100
: Colors.white,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(productDataListDisplay
.elementAt(index)
.productId
.toString()),
Center(
child: Text(
productDataListDisplay
.elementAt(index)
.productName
.toString(),
),
),
Center(
child: Text(
"${productDataListDisplay.elementAt(index).productKG.toString()} KG",
),
),
Center(
child: Text(
"${productDataListDisplay.elementAt(index).productCategoryId.toString()}",
),
),
],
),
),
);
},
),
),
],
),
)
: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue)),
),
),
);
}
void filterProduct(allProducts) {
productDataListDisplay.clear();
if (selectProductCategoryDataList != null &&
selectProductCategoryDataList.isNotEmpty) {
final List<int> idsOfSelectedCategories = selectProductCategoryDataList
.map((category) => category.productCategoryId!)
.toList();
print("idsOfSelectedCategories=> $idsOfSelectedCategories");
Set<ProductDataModel> storesInSelectedCategories =
getStoresFromSelectedCategories(allProducts, idsOfSelectedCategories);
setState(() {
productDataListDisplay.addAll(storesInSelectedCategories);
print(allProducts.length);
print(storesInSelectedCategories.length);
});
} else {
setState(() {
productDataListDisplay.addAll(allProducts);
});
}
}
Set<ProductDataModel> getStoresFromSelectedCategories(
List<ProductDataModel> allProducts, List<int> idsOfSelectedCategories) {
Set<ProductDataModel> allProductsFromCategory = {};
for (var categoryId in idsOfSelectedCategories) {
var productMatched = allProducts
.where((store) => store.productCategoryId!
.split(" ")
.contains(categoryId.toString()))
.toList();
print("Stores Matched runtime=>${productMatched.runtimeType}");
allProductsFromCategory.addAll(productMatched);
}
return allProductsFromCategory;
}
}
Add .then((value) {if (mounted) {setState(() {});}}); to showdialog so that state gets refreshed when alertdialog closes.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<ProductDataModel> productDataList = [];
Set<ProductDataModel> productDataListDisplay = {};
List<ProductCategoryDataModel> productCategoryDataList = [];
List<ProductCategoryDataModel> selectProductCategoryDataList = [];
ScrollController scrollController = ScrollController();
bool isShowLoader = false;
getProductList() async {
try {
setState(() {
isShowLoader = true;
});
final response = await http.get(Uri.parse(productDataUrl));
log("Response URL=> ${response.toString()}");
if (response.statusCode == 200) {
var decode = jsonDecode(response.body);
log("Response Body=> ${decode.toString()}");
for (int i = 0; i < decode.length; i++) {
productDataList.add(ProductDataModel.fromJson(decode[i]));
}
setState(() {
isShowLoader = false;
});
return productDataList;
} else {
setState(() {
isShowLoader = false;
});
throw "Unable to retrieve product data.";
}
} catch (e) {
setState(() {
isShowLoader = false;
});
print('Something went wrong.');
}
}
getProductCategoryList() async {
try {
setState(() {
isShowLoader = true;
});
final response = await http.get(Uri.parse(productCategoryDataUrl));
log("Response URL=> ${response.toString()}");
if (response.statusCode == 200) {
var decode = jsonDecode(response.body);
log("Response Body=>${decode.toString()}");
for (int i = 0; i < decode.length; i++) {
productCategoryDataList
.add(new ProductCategoryDataModel.fromJson(decode[i]));
}
setState(() {
isShowLoader = false;
});
return productCategoryDataList;
} else {
setState(() {
isShowLoader = false;
});
throw "Unable to retrieve product data.";
}
} catch (e) {
setState(() {
isShowLoader = false;
});
print('Something went wrong.');
}
}
#override
void initState() {
getProductList();
getProductCategoryList();
super.initState();
}
#override
Widget build(BuildContext context) {
filterProduct(productDataList);
return Scaffold(
body: SafeArea(
child: isShowLoader == false
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
titlePadding: EdgeInsets.zero,
backgroundColor: const Color(0xFF242424),
title: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(
Icons.close,
color: Colors.white,
size: 25,
))
],
),
contentPadding: EdgeInsets.zero,
content: Container(
padding: EdgeInsets.all(5),
width: double.maxFinite,
child: ListView(
padding: EdgeInsets.all(8.0),
children: [
Text(
"dfff ${productCategoryDataList[0].isChecked}",
style: const TextStyle(
color: Colors.white,
fontSize: 16),
),
...productCategoryDataList
.map((e) => CheckboxListTile(
controlAffinity:
ListTileControlAffinity
.leading,
title: Text(
"${e.productCategoryName}",
style: const TextStyle(
color: Colors.white,
fontSize: 16),
),
value: e.isChecked,
selected:
selectProductCategoryDataList
.contains(e),
onChanged: (val) {
print("val: $val");
setState(() {
e.isChecked = val;
selectProductCategoryDataList
.contains(e)
? selectProductCategoryDataList
.remove(e)
: selectProductCategoryDataList
.add(e);
print(
"_isChecked: ${e.isChecked}");
});
},
))
],
),
),
);
},
);
}).then((value) {
if (mounted) {
setState(() {});
}
});
},
style: ElevatedButton.styleFrom(
primary: Colors.black,
onPrimary: Colors.white,
minimumSize: Size(
MediaQuery.of(context).size.width * 0.30,
MediaQuery.of(context).size.height * 0.05)),
child: const Text(
"Filter",
style: TextStyle(fontSize: 15),
),
),
Container(
child: ListView.builder(
itemCount: productDataListDisplay.length,
shrinkWrap: true,
controller: scrollController,
itemBuilder: (context, index) {
return Card(
child: Container(
padding: EdgeInsets.only(top: 15, bottom: 15),
color: (index % 2 == 0)
? Colors.grey.shade100
: Colors.white,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(productDataListDisplay
.elementAt(index)
.productId
.toString()),
Center(
child: Text(
productDataListDisplay
.elementAt(index)
.productName
.toString(),
),
),
Center(
child: Text(
"${productDataListDisplay.elementAt(index).productKG.toString()} KG",
),
),
Center(
child: Text(
"${productDataListDisplay.elementAt(index).productCategoryId.toString()}",
),
),
],
),
),
);
},
),
),
],
),
)
: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue)),
),
),
);
}
void filterProduct(allProducts) {
productDataListDisplay.clear();
if (selectProductCategoryDataList != null &&
selectProductCategoryDataList.isNotEmpty) {
final List<int> idsOfSelectedCategories = selectProductCategoryDataList
.map((category) => category.productCategoryId!)
.toList();
print("idsOfSelectedCategories=> $idsOfSelectedCategories");
Set<ProductDataModel> storesInSelectedCategories =
getStoresFromSelectedCategories(allProducts, idsOfSelectedCategories);
setState(() {
productDataListDisplay.addAll(storesInSelectedCategories);
print(allProducts.length);
print(storesInSelectedCategories.length);
});
} else {
setState(() {
productDataListDisplay.addAll(allProducts);
});
}
}
Set<ProductDataModel> getStoresFromSelectedCategories(
List<ProductDataModel> allProducts, List<int> idsOfSelectedCategories) {
Set<ProductDataModel> allProductsFromCategory = {};
for (var categoryId in idsOfSelectedCategories) {
var productMatched = allProducts
.where((store) => store.productCategoryId!
.split(" ")
.contains(categoryId.toString()))
.toList();
print("Stores Matched runtime=>${productMatched.runtimeType}");
allProductsFromCategory.addAll(productMatched);
}
return allProductsFromCategory;
}
}
I need help in this please i have a RefreshIndicator that loads a ListView when there is data in the database however I want to make the widget return Text widget 'no data' if the table empty instead of the ListView.
but the when the table is empty it error appear
_TypeError (type 'String' is not a subtype of type 'Iterable')
and that will appear in the refresh method i don't know what should i do i appreciate any help thanks in advance.
this the UI for the page
class _onlineResultTab extends State<onlineResultTab> {
List<ResultsModelClass> resultslist = new List();
List<CoursesModelClass> coursesist = new List();
ResultsModelClass resultsModelClass;
CoursesModelClass courseModelClass;
final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey<RefreshIndicatorState>();
DataApiProvider dataApiProvider = new DataApiProvider();
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
key: refreshKey,
onRefresh: refreshList,
child: _resultwidget(context),
),
);
}
fetchResultDetails() async {
final allRows = await DbHelper.mydb.getOnlineResultList();
allRows.forEach((row) => {
resultsModelClass = ResultsModelClass(
"",
row["Res_Stud_Index"],
row["Res_Stud_Name"],
row["Res_Sem"].toString(),
"",
"",
row["Res_Batch"],
row["Res_Courses"],
row["Res_Grade"],
row["Res_GPA"].toString(),
row["Res_CGPA"],
row["Res_Status"],
row["faculty_desc_e"],
row["major_desc_e"]),
resultslist.add(resultsModelClass)
});
if (this.mounted) {
setState(() {});
}
}
fetchCourse() async {
final allRows = await DbHelper.mydb.getResultcoursesList();
allRows.forEach((row) => {
courseModelClass = CoursesModelClass(
row["Res_Courses"],
row["Res_Grade"],
),
coursesist.add(courseModelClass)
});
if (this.mounted) {
setState(() {});
}
}
List<String> listHeader = ['Student Results Details'];
Widget _resultwidget(context) {
final _size = MediaQuery.of(context).size;
return resultslist.length == 0
? ListView.builder(
controller: ScrollController(),
itemCount: 1,
itemBuilder: (context, index) {
return Center(
child: Padding(
padding: EdgeInsets.only(top: _size.height * 0.4),
child: Text(
"No Result Details !",
style: TextStyle(fontSize: 20),
),
));
},
physics: AlwaysScrollableScrollPhysics(),
)
: new ListView.builder(
itemCount: listHeader.length,
itemBuilder: (context, index) {
//var _size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: Center(
child: Padding(
padding: EdgeInsets.only(bottom: _size.height * 0.02),
child: Column(
children: [
Card(
elevation: 20,
margin: EdgeInsets.symmetric(
horizontal: _size.width * 0.005,
),
child: Container(
height: 50.0,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor),
child: Center(
child: Text(
'Semester ' +
resultslist[0].Res_Sem +
' Result ',
style: TextStyle(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold),
),
),
),
),
],
))),
);
},
);
}
the refresh method
Future<String> refreshList() async {
refreshKey.currentState?.show(atTop: false);
//await Future.delayed(Duration(seconds: 10));
var studentIndex;
final allRows = await DbHelper.mydb.MainInfoCehck();
allRows.forEach((row) => {
row["Stud_Index"],
studentIndex = row["Stud_Index"].toString()
//print( row["Stud_Index"].toString())
});
print(studentIndex);
//void refreshResult(String studentIndex)async{
await dataApiProvider.refresh_result(studentIndex);
// }
if (this.mounted) {
setState(() {
coursesist.clear();
resultslist.clear();
fetchResultDetails();
fetchCourse();
});
}
return null;
}
Future<String> refresh_result(String studentIndex) async {
var url = main_urll + "?studentIndex=" + studentIndex.trim();
final response = await http.get(url);
List<ResultDetailsModel> ResulttDetailsListModel = [];
final resultInfo = jsonDecode(response.body)['result_info'];
DbHelper.mydb.deleteTableData("Result");
print('Result informations : ' + resultInfo.toString());
for (Map i in resultInfo) {
ResulttDetailsListModel.add(ResultDetailsModel.fromJson(i));
DbHelper.mydb.saveResultDetails(ResultDetailsModel.fromJson(i));
}
return "";
}
Please check if
resultslist == null or resultslist.isNotEmpty()
for Handling empty list
I am trying to integrate pagination with gridview.builder,it's working in listview without any problem but in gridview it doesn't work as expected,The problem i am facing is The API data is returns 20 data per page,
Problem:In API it returns 20 data/page,in this API it returns total of 21 data,in 1 st page contains 20 data 2nd page contains only 1 data the pagination is working i can load data but i cannot scroll back to page 1 data it just stuck with page 2 data
I am following this Tutorial,this is working with listview without no problem but in gridview the above problem occurs
InitState and Variable initialization
var page = 1;
ScrollController _sccontroller = new ScrollController();
#override
void initState() {
super.initState();
Future token = SharedPrefrence().getToken();
token.then((data) async {
userToken = data;
this.getSearchResult(page);
_sccontroller.addListener(() {
if (_sccontroller.position.pixels ==
_sccontroller.position.maxScrollExtent) {
getSearchResult(page);
}
});
});
setState(() {
title = name;
});
}
#override
void dispose() {
_sccontroller.dispose();
super.dispose();
}
Gridview
GridView.builder(
controller: _sccontroller,
itemCount: search_result_list.length + 1,
physics: const AlwaysScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(childAspectRatio: 5.2 / 8.0,
crossAxisCount: 2, crossAxisSpacing: 3.0, mainAxisSpacing: 3.0),
itemBuilder: (BuildContext context, int index) {
if (index == search_result_list.length) {
return Center(child: Padding(padding: const EdgeInsets.all(8.0), child: CircularProgressIndicator(),));
} else {
return Container(
child: Padding(
padding: const EdgeInsets.only(top:10),
child: GestureDetector(
onTap: () {
},
child: Container(
// height: 700,
child: Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2),
),
child: Container(
width: 300,
//height: 500,
child: Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
//crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(2),
child: Image.network(
Urls.baseImageUrl +
search_result_list[index]
.thumb_path,
fit: BoxFit.fill,
height: 180,
width: 180,
),
),
Padding(
padding: const EdgeInsets.all(5),
child: Align(
child: Text(
search_result_list[index].name,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
alignment: Alignment.centerLeft,
)),
],
),
],
)),
),
),
),
),
);
}
},
)
API Function
Future<void> getSearchResult(int index) async {
print("size" + index.toString());
if (!isLoading) {
setState(() {
isLoading = true;
});
// ProgressDialog dialog = CustomDialogs().showLoadingProgressDialog(context);
List<PrefrenceModel> tList = List();
var response = await http.get(
"${Urls.baseUrl}${Urls.CategorySearch}?latitude=${Constants
.latitude}&longitude=${Constants
.longitude}&radius=100&locale=en&keyword=${slug}&page=${index
.toString()}&type=item",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer ${userToken}",
"Accept": "application/json"
},
);
Map<String, dynamic> value = json.decode(response.body);
if (response.statusCode == 200) {
// dialog.dismissProgressDialog(context);
try {
print("response search " + response.body.toString());
Map<String, dynamic> value = json.decode(response.body);
var status = value['success'];
var message = value['message'];
last_page = value['last_page'];
print(message);
if (status == true) {
search_result_list.clear();
try {
var data = value['data']['data'];
if (data.length > 0) {
for (int i = 0; i < data.length; i++) {
var obj = data[i];
//var petrol_obj = obj['petrolstation'];
search_result_list.add(PrefrenceModel(
obj['id'].toString(),
obj['description'].toString(),
obj['valid_from'].toString(),
obj['valid_to'].toString(),
obj['thumbs']['md'].toString(),
obj['status'].toString(),
"",
"",
obj['name'].toString(),
"",
obj['flyer_slug'].toString(),
obj['type'].toString(),
obj['isClipped'],
obj['url'].toString(),
obj['price'].toString(),
obj['flyer_page_id'].toString(),
obj['id'].toString(),
obj['shop_name'].toString(),
obj['shop_logo'].toString(),
obj['brand_name'].toString(),
obj['brand_logo'].toString(),
));
}
setState(() {
isLoading = false;
search_result_list.addAll(tList);
page++;
});
} else {
final snackBar = SnackBar(content: Text("No Data Available"));
_scaffoldKey.currentState.showSnackBar(snackBar);
}
} catch (e) {
e.toString();
}
} else {
print("Error...");
final snackBar = SnackBar(content: Text(message));
_scaffoldKey.currentState.showSnackBar(snackBar);
}
} catch (e) {
print(e.toString());
}
} else {
var message = value['message'];
// dialog.dismissProgressDialog(context);
final snackBar = SnackBar(content: Text(message));
_scaffoldKey.currentState.showSnackBar(snackBar);
}
}
else
{
setState(() { isLoading = false; });
}
}
Pagination will gives you the data related to the page, for example page one will returns data from 1 to 20, page two from 21 to 40. so you have to add data of page 2 to existed data from page 1. but in your case you are calling search_result_list.clear(); this will erase the old data and put only the new data into the list.