I want to have a pagination in my grid list.
I have a list of items which is coming from an API endpoint. and currently I am displaying 21 records. but the original list is very long. so, I want to have a pagination type where users can see all the items from the list coming from API.
here's my code.
class _CategoryPageState extends State<CategoryPage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getCategoryList(widget.slug, widget.isSubList, widget.token),
builder: (context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return CircularProgress();
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return createListView(context, snapshot, widget.isSubList, widget.token);
}
},
);
}
}
Widget createListView(BuildContext context, AsyncSnapshot snapshot, bool isSubList, String token) {
List<CategoryModel> values = snapshot.data;
return GridView.count(
crossAxisCount: 3,
// physics: NeverScrollableScrollPhysics(),
// values[index].imageUrl
padding: EdgeInsets.all(1.0),
/*childAspectRatio: 8.0 / 9.0,*/
children: List<Widget>.generate(values.length, (index) {
if(values[index].imageUrl == null){
values[index].imageUrl = 'dummy.jpg';
}
return GridTile(
child: GridTilesCategory(
name: values[index].name,
imageUrl: values[index].imageUrl,
slug: values[index].slug,
fromSubProducts: isSubList,
token: token,
imageType: 'category-images',
));
}),
);
}
Future<List<CategoryModel>> getCategoryList(String slug, bool isSubList, String token) async {
if (isSubList) {
categories = null;
}
if (categories == null) {
final response = await http.post(
Urls.ROOT_URL + slug,
body: json.encode({
"filter" : {"categoryname":null,"status" : 1},
"sortOrder" : "ASC",
"sortField" : "",
"pageNumber" : 0,
"pageSize" : 21
}),
headers: {
'content-type': 'application/json',
'Authorization': 'Bearer $token',
}
);
print(response.toString());
final responseData = json.decode(response.body);
int statusCode = response.statusCode;
final body = json.decode(response.body);
print(body);
if (statusCode == 200) {
categories = (body['items'] as List).map((i) => CategoryModel.fromJson(i)).toList();
return categories;
} else {
return categories = List();
}
} else {
return categories;
}
}
my requirement is like a continue scrolling type pagination.
Sice No one found this question worthy enough to answer.
I have found a way to implement pagination. here's my final code.
class _ProductListWidgetState extends State<ProductListWidget> {
int pageNumber = 0;
int totalCount = 0;
int totalPages = 0;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getProductList(widget.slug, false, widget.token, widget.name, widget.identifier, pageNumber),
builder: (context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return CircularProgress();
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return createListView(context, snapshot, widget.token);
}
},
);
}
ProductsModels products;
Future<ProductsModels> getProductList(String slug, bool isSubList, String token, String name, String identifier, int page) async {
if (isSubList) {
products = null;
}
final response = await http.post(
Urls.ROOT_URL + slug,
body: json.encode({
"filter" : {"categoryname":identifier,"status" : 1},
"sortOrder" : "ASC",
"sortField" : "",
"pageNumber" : page,
"pageSize" : 20
}),
headers: {
'content-type': 'application/json',
'Authorization': 'Bearer $token',
}
);
int statusCode = response.statusCode;
final body = json.decode(response.body);
if (statusCode == 200) {
products = ProductsModels.fromJson(body);
totalCount = products.count;
totalPages = totalCount~/20;
print('total count: $totalCount');
print('total pages: $totalPages');
return products;
} else {
return products = ProductsModels();
}
}
Widget createListView(BuildContext context, AsyncSnapshot snapshot, String token) {
ProductsModels values = snapshot.data;
List<Results> results = values.results;
print(results.toString());
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
children: [
GridView.count(
shrinkWrap: true,
primary: true,
physics: NeverScrollableScrollPhysics(),
crossAxisCount: 2,
padding: EdgeInsets.all(1.0),
children: List<Widget>.generate(results.length, (index) {
if(results[index].imageUrls == null){
results[index].imageUrls = 'dummy.jpg';
}
return GridTile(
child: GridTilesProducts(
name: results[index].name,
imageUrl: results[index].imageUrls,
slug: results[index].slug,
price: results[index].price.toString(),
token: token,
));
}),
),
Divider(height: 1.0, color: Color(0xFFe2e2e2),thickness: 1.0,),
Padding(
padding: EdgeInsets.all(20),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
OutlineButton(
borderSide: BorderSide(color: Color(0xFFe1e5e8)),
onPressed: (){
if((pageNumber > totalPages) || (pageNumber > 0)){
setState(() {
pageNumber--;
});
}
else{
Toast.show("Page 1 Reached", context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM);
}
},
color: Color(0xFFe1e5e8),
textColor: Color(0xFFe1e5e8),
child: Container(
height: 30.0,
child: Center(
child: Text('Previous Page',
style: TextStyle(fontSize: 15.0, fontFamily: 'Brand-Bold', color: Color(0xFF383635))),
),
)
),
RaisedButton(
onPressed: (){
if(pageNumber < totalPages){
setState(() {
pageNumber++;
});
}
else{
Toast.show("No More Items Found", context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM);
}
},
color: Color(0xFFd16608),
textColor: Colors.white,
child: Container(
height: 30,
child: Center(
child: Text(
'Next Page',
style: TextStyle(fontSize: 15, fontFamily: 'Poppins-Bold'),
),
),
),
)
],
),
),
],
),
);
}
}
try to use ListView.builder like that:
ListView.builder(
key: PageStorageKey<String>('savePositionKey'),
itemCount: widget.activities.length + 1,
itemBuilder: (builderContext, index) {
if (index == (widget.activities.length - 1)) {
getNextActivitiesPage();
}if (index == (widget.activities.length)) {
return !widget.activitiesFetchState.isCompleted() &&
!widget.isNewData //when you start again from page 0
? Center(child: CircularProgressIndicator())
: SizedBox.shrink();
}
you code- on tap and more.....
);
and in your manager create getNext function that gets the next page and insert it to the end of the list
Related
I have been trying with last an hour but not getting solution and failing completely to understand why its showing an error...
I have created a function for fetching data,
I have placed print statement for seeing what does it returns...here it is printing data but while inside feature builder it showing an error...
when I run app its showing output with
list<dynamic> is not a subtype of type FutureOr<List<Map<String,dynamic>>
it means its executes snapshot.haserror part
here is my code
class _HomeScreenState extends State<HomeScreen> {
Future<List<Map<String,dynamic>>> fetchdata() async {
var resp =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/photos"));
print("fetchdata function showing"+json.decode(resp.body).toString());
return json.decode(resp.body);
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: MyBody(),
),
);
}
MyBody() {
return FutureBuilder<List<Map<String,dynamic>>>(
future: fetchdata(),
builder: (context, snapshot) {
print("Futurebuilder showing:"+snapshot.toString());
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
print('againt'+snapshot.toString());
List<Map<String,dynamic>> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Text(data[index]['title']));
});
}
}
},
);
}}
Future<List<Map<String, dynamic>>> fetchdata() async {
var resp = await http
.get(Uri.parse("https://jsonplaceholder.typicode.com/photos"));
print("fetchdata function showing" + json.decode(resp.body).toString());
List<dynamic> result = jsonDecode(resp.body);
return result.map((e) => e as Map<String, dynamic>).toList();
}
just change your function like this
Your API Call:
Future<List<dynamic>> getJobsData() async {
String url = 'https://jsonplaceholder.typicode.com/photos';
var response = await http.get(Uri.parse(url), headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
});
return json.decode(response.body);
}
Your Widget:
Center(
child: FutureBuilder<List<dynamic>>(
future: getJobsData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
var title = snapshot.data![index]['title'];
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
child: ListTile(
title: Text(title),
),
);
},
),
);
}
return CircularProgressIndicator();
},
),
),
Result->
I have a method that fetches a PatientLog from SQLite.However, This PatientLog table mapped to an object with a class named PatientLog. Inside this PatientLog class, several other objects such as Speciality, AttendingPhysician, Course, etc. I need to map these PatienLog records to a local object. However, I have to use nested Futures. I need to retrieve the data from this nested Future. Think of Future of Future.
This is my fetch method
Future<List<Future<PatientLog>>> getForms() async {
Database db = await instance.getDatabase;
List<Map<String, dynamic>> forms =
await db.query(_tablePatientLog, orderBy: 'id DESC');
Institute? institute;
AttendingPhysician? attendingPhysician;
Speciality? speciality;
Course? course;
List<Future<PatientLog>> list = forms.map((myMap) async {
int? courseId = myMap['course_id'] as int?;
int? specialityId = myMap['speciality_id'] as int?;
int? attendingId = myMap['attending_id'] as int?;
int? instituteId = myMap['institute_id'] as int?;
if (courseId != null) {
await getCourse(courseId).then((value) => course=value);
}
if (attendingId != null) {
await getAttending(attendingId).then((value) => attendingPhysician=value);
}
if (specialityId != null) {
await getSpeciality(specialityId).then((value) => speciality=value);
}
if (instituteId != null) {
await getInstitute(instituteId).then((value) => institute=value);
}
return PatientLog.fromMap(
myMap, institute, course, attendingPhysician, speciality);
}).toList();
return list;
}
I need to display that information on a screen. I get an error type 'List<Future<PatientLog>>' is not a subtype of type 'Future<Object?>?'
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
super.initState();
_refresh();
}
late List<Future<PatientLog>> fromDatabase;
Future<dynamic> _refresh() async {
await _helper.getForms().then((value) async{
setState(() {
fromDatabase = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _helper.getForms(),
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return Center(
child: Text(
"Henüz kaydedilmiş taslak bulunmamaktadır.",
textAlign: TextAlign.center,
style: TEXT_STYLE,
));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Sanırım bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: snapshot.data,
builder: (context,innerSnap) {
return Text(innerSnap.toString());/*CustomListTile(
formData: innerSnap.data[index],
index: index,
routeTo: 1,
isDeletable: true,
);*/
}
);
},
),
),
);
}
return const Center(
child: Text("Nothing")//spinkit,
);
}),
);
}
}
This function have futureBuilder inside and returns another function that returns ListView.
Also I do not want the user to download information from the Internet every time they visit the page, so I put the information downloaded from the Internet into a static variable in class, so i make if else. If this static variable length != 0 , it return me listView instead of make server request with futureBuilder
function that works 2 times instead of one is ListView body
And also this bug created after i added purchases to my application, maybe something wrong there. I don't have any ideas why this happend
this is my complete code
class AddCheckList extends StatefulWidget {
const AddCheckList({Key? key}) : super(key: key);
#override
_AddCheckListState createState() => _AddCheckListState();
}
class _AddCheckListState extends State<AddCheckList> {
String xQueryReqestForCheckListNames = "element CheckListList {for \$a in PACK/OBJECT where (\$a/#inUse = 'True') order by \$a/#name return element CheckList {attribute name {\$a/#name}, attribute sPaid {\$a/#isPaid},attribute oid {\$a/#oid} }}";
String serverLink = "http...";
int addCheckListMethod = 0;
// if addCheckListMethod == 0, then data will download from server and checkLists will be added from server xml data
// if addCheckListMethod == 1, then data will download from assets, and checkLists will be added from assets xml data with getXmlData function
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final String _productID = '1d7ea644f690ffa';
bool _available = true;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
StreamSubscription<List<PurchaseDetails>>? _subscription;
#override
void initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
setState(() {
_purchases.addAll(purchaseDetailsList);
_listenToPurchaseUpdated(purchaseDetailsList);
});
}, onDone: () {
_subscription!.cancel();
}, onError: (error) {
_subscription!.cancel();
});
_initialize();
super.initState();
}
#override
void dispose() {
_subscription!.cancel();
super.dispose();
}
void _initialize() async {
_available = await _inAppPurchase.isAvailable();
List<ProductDetails> products = await _getProducts(
productIds: Set<String>.from(
[_productID],
),
);
setState(() {
_products = products;
});
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
// _showPendingUI();
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
// bool valid = await _verifyPurchase(purchaseDetails);
// if (!valid) {
// _handleInvalidPurchase(purchaseDetails);
// }
break;
case PurchaseStatus.error:
print(purchaseDetails.error!);
// _handleError(purchaseDetails.error!);
break;
default:
break;
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
});
}
Future<List<ProductDetails>> _getProducts({required Set<String> productIds}) async {
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(productIds);
return response.productDetails;
}
void _subscribe({required ProductDetails product}) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
_inAppPurchase.buyNonConsumable(
purchaseParam: purchaseParam,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(padding: EdgeInsets.all(14.0), child: listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, _products,_subscribe)),
);
}
}
Widget listView(addCheckListMethod, serverLink, product,_subscribe) {
return ListView.separated(
itemCount: CheckListModel.checkListModelNamesFromServer.length,
itemBuilder: (BuildContext context, int index) {
if (CheckListModel.checkListModelNamesFromServer[index].ispaid == false) {
return InkWell(
onTap: () async {
CheckListModel checkList = CheckListModel('', 0, '', 0, 0, 0, '', [], '');
if (addCheckListMethod == 0) {
String xQueryReqestForCheckList = 'for \$a in PACK/OBJECT where \$a/#name="${CheckListModel.checkListModelNamesFromServer[index].name}" return \$a';
var data = await CheckListModel.getDataFromServer(xQueryReqestForCheckList, serverLink);
CheckListModel.addCheckListFromServer(checkList, data);
} else {
CheckListModel.addCheckList(index, checkList);
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Добавить описание"),
content: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
child: TextField(
decoration: new InputDecoration.collapsed(hintText: "Описание", border: InputBorder.none),
maxLines: null,
onChanged: (String value) async {
checkList.description = value;
},
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Отменить"),
),
TextButton(
onPressed: () async {
await checkList.writeToFile(checkList.toJson());
CheckListModel.checkLists.add(checkList);
Navigator.pushNamed(context, '/beforeMainCheckList', arguments: {'index': CheckListModel.checkLists.length - 1});
},
child: Text('Добавить'))
],
);
});
},
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 30),
)
],
),
),
);
} else {
if (product.length != 0) {
return showPurchaseCheckLists(product, index,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
},
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 14,
);
});
}
Widget showPurchaseCheckLists(product, index,_subscribe) {
int getCurrentProduct() {
String? checkListModelid = CheckListModel.checkListModelNamesFromServer[index].checkListId?.toLowerCase();
int indexx = 0;
for (int i = 0; i < product.length; i++) {
if (checkListModelid == product[i].id) {
indexx = i;
}
}
return indexx;
}
return InkWell(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10), color: Color.fromARGB(255, 240, 240, 240)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 25),
),
Text("Купить за ${product[getCurrentProduct()].price}")
],
),
),
onTap: () {
_subscribe(product: product[getCurrentProduct()]);
},
);
}
Widget listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, product,_subscribe) {
if (CheckListModel.checkListModelNamesFromServer.length == 0) {
return FutureBuilder(
future: CheckListModel.getDataFromServer(xQueryReqestForCheckListNames, serverLink),
builder: (context, data) {
if (data.connectionState == ConnectionState.done) {
return listView(addCheckListMethod, serverLink, product,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
} else {
return listView(addCheckListMethod, serverLink, product,_subscribe);
}
}
Here I've one service page where I've displayed all the services, and there I can select multiple services. but I want to select only one service. I've used the checkbox Listitle for the service selection. I want that user can select only one service not multiple services at a time.
Here is code i've tried :
class _AddWalkinServiceScreenState extends State<AddWalkinServiceScreen>
with TickerProviderStateMixin {
List<int> servicesIds = [];
int selected = 0;
Map<String, bool> _selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
List<dynamic> finalList = new List();
List<dynamic> searchList = new List();
bool isLoading = false;
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeInQuint);
controller.forward();
}
Widget _renderServices(AddWalkinServiceViewModel awsVm) {
List lovCountryServices = searchList.length != 0 ? searchList : finalList;
if (lovCountryServices == null || lovCountryServices.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
}
// print(lovCountryServices);
return Container(
child: finalList.length < 1
? ListTile(
leading: CircularProgressIndicator(),
)
: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item = lovCountryServices[
index]; // should be outside build function
List items = item['services'];
return ExpansionTile(
title: Text(item['name']),
children: List.generate(items.length, (i) {
_selection[items[i]['name']] =
_selection[items[i]['name']] ?? items[i]['isSelected'];
return CheckboxListTile(
title: Text(items[i]['name']),
value: _selection[items[i]['name']] == null
? false
: _selection[items[i]['name']],
onChanged: (val) {
setState(() {
_selection[items[i]['name']] = val;
if (val) {
servicesIds.add(items[i]['id']);
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
SubCategory subService = new SubCategory(
id: items[i]['id'],
name: items[i]['name'],
);
List<SubCategory> subCategories = [];
if (services.length < 1) {
subCategories.add(subService);
selectedServices.add(
new BspServices.Service(
mainCategory: item['name'],
mainCategoryId: item['id'],
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
servicesIds.removeWhere((service) {
return service == items[i]['id'];
});
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == items[i]['id'];
});
}
});
print('servicesIds after set state');
print(servicesIds);
},
);
}),
);
},
),
);
}
Widget content(BuildContext context, AddWalkinServiceViewModel awsVm) {
Orientation orientation = MediaQuery.of(context).orientation;
var colorStyles = Theming.colorstyle(context);
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
_searchFilter();
},
defaultBar: AppBar(
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.pop(context);
// NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
return new Scaffold(
backgroundColor: colorStyles['primary'],
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: FadeTransition(
opacity: animation,
child: new Container(
margin: EdgeInsets.only(top: 10),
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(50.0),
topRight: const Radius.circular(50.0),
),
),
child: isLoading ? FadeInUi() : _renderServices(awsVm),
),
),
);
}
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, AddWalkinServiceViewModel>(
converter: (Store<AppState> store) =>
AddWalkinServiceViewModel.fromStore(store),
onInit: (Store<AppState> store) {
print('store.state.servicesState.servicesByCountry');
print(store
.state.servicesState.servicesByCountry.servicesByCountry[0].name);
Map<String, dynamic> services =
store.state.servicesState.servicesByCountry.toJson();
finalList = services['servicesByCountry'];
print('finalList = $finalList');
},
builder: (BuildContext context, AddWalkinServiceViewModel awsVm) =>
content(context, awsVm),
);
}
}
UPDATED
List<Map> services = [];
List<int> selections = [];
#override
void initState() {
super.initState();
getList();
}
void getList() async {
//get data from internet/api
//for ex. I m using offline data
setState(() {
services = List.generate(
10,
(ind) => {
'name': 'Service Category $ind',
'services': ['Service 1', 'Service 2']
}).toList();
selections = List.generate(10, (ind) => -1).toList();
});
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: services.length < 1
? ListTile(
leading: CircularProgressIndicator(), title: Text('Loading...'))
: ListView.builder(
itemCount: services.length,
itemBuilder: (con, ind) {
return ExpansionTile(
title: Text('${services[ind]['name']}',
style: TextStyle(color: Colors.black)),
children:
List.generate(services[ind]['services'].length, (ii) {
return CheckboxListTile(
title: Text('${services[ind]['services'][ii]}',
style: TextStyle(color: Colors.green[900])),
value: selections[ind] == ii,
onChanged: (b) {
setState(() {
selections[ind] = ii;
});
});
}).toList());
}));
}
i'm trying to implement pull_to_refresh . I have two stateless widget that i will use . First is _ProductCategoryDetailPageState here is the code
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: AppBar(
title: Text(widget.categoryName),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: Column(
children: <Widget>[
Divider(
height: 20.0,
),
Flexible(
child:StreamBuilder<List<Products>>(
stream: _productController.stream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return errMess(context, "Failed to fetch data");
} else {
if (snapshot.hasData) {
if (snapshot.data.length > 0) {
return ProductList(category: snapshot.data);
} else {
return errMess(context,
"There is no available product in this category");
}
} else {
return errMess(context,
"There is no available product in this category");
}
}
},
)),
Divider(
height: 25.0,
),
],
),
)));
}
loadProduct(String categoryId, int limit, int offset) async {
List<Products> products = await fetchProducts(http.Client(), categoryId, limit, offset);
_productController.sink.add(products);
}
static List<Products> parseProducts(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Products>((json) => Products.fromJson(json)).toList();
}
Future<List<Products>> fetchProducts(http.Client client, String categoryId, int limit, int offset) async {
final response = await http.post(Configuration.url +
"api/getProducts/" +
categoryId +
"/" +
limit.toString() +
"/" +
offset.toString());
if (response.statusCode < 200 || response.statusCode > 300) {
throw new Exception('Failed to fetch data');
} else {
return compute(parseProducts, response.body);
}
}
and here is my second stateless widget
class _ProductListState extends State<ProductList> {
int limit = 0;
int offset = 4;
RefreshController _refreshController2 =
RefreshController(initialRefresh: false);
#override
Widget build(BuildContext context) {
return SmartRefresher(
child: new GridView.builder(
itemCount: widget.category.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
onTap: () {
print("Product detail");
},
child: Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Image.network(
Configuration.url +
"assets/app_assets/" +
widget.category[index].productImage,
width: 250,
height: 250,
filterQuality: FilterQuality.low,
),
),
SizedBox(
height: 25,
),
Text(
widget.category[index].productName,
style: TextStyle(fontSize: 15.0),
),
SizedBox(
height: 25,
),
],
),
),
);
},
),
controller: _refreshController2,
enablePullUp: true,
header: MaterialClassicHeader(),
onRefresh: () {
_onRefresh(_refreshController2, widget.category,
widget.category[0].categoryId);
},
onLoading: () {
_onLoading(_refreshController2, widget.category,
widget.category[0].categoryId);
},
);
}
void _onLoading(RefreshController controller, List<Products> data,String categoryId) async {
await Future.delayed(Duration(milliseconds: 2000));
setState(() {
limit = limit + offset;
offset = 6;
});
_ProductCategoryDetailPageState().loadProduct(categoryId, limit, offset);
controller.loadComplete();
}
void _onRefresh(RefreshController controller, List<Products> data,
String categoryId) async {
await Future.delayed(Duration(milliseconds: 1000));
controller.refreshCompleted();
}
}
when i pull the grid there is no error , but the data is not change. After i check on this part
Flexible(
child:StreamBuilder<List<Products>>(
stream: _productController.stream,
builder: (context, snapshot) {
print("run")
if (snapshot.hasError) {
return errMess(context, "Failed to fetch data");
} else {
if (snapshot.hasData) {
if (snapshot.data.length > 0) {
return ProductList(category: snapshot.data);
} else {
return errMess(context,
"There is no available product in this category");
}
} else {
return errMess(context,
"There is no available product in this category");
}
}
},
)),
As you can see i adding print("run") , it just only showing once.
my full script https://gist.github.com/bobykurniawan11/04f2584c6de97f1d9324bfe3b24f669f
This won't work as you are creating a new State instance of the State object. You should connect both widgets with a callback for example.
_ProductCategoryDetailPageState().loadProduct(categoryId, limit, offset);
Like this:
// Custom callback function
typedef void OnLoadProductsFunction(String categoryId, int limit, int offset);
class ProductList extends StatefulWidget {
OnLoadProductsFunction onLoad;
ProductList({
this.category,
this.onLoad,
})
}
...
void _onLoading(RefreshController controller, List<Products> data,String categoryId) async {
await Future.delayed(Duration(milliseconds: 2000));
setState(() {
limit = limit + offset;
offset = 6;
});
widget.onLoad(categoryId, limit, offset);
controller.loadComplete();
}
...
// In the parent widget
return ProductList(
category: snapshot.data
onLoad: (categoryId, limit, offset) {
loadProduct(categoryId, limit, offset);
}
);
Doing it this way the streamcontroller will be updated from the callback function. Other option you have is to pass the StreamController instance to the ProductList widget and is the child who adds the list of products to the sink.