import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:get/get.dart';
import 'package:mkpos/controller/index_controller.dart';
import 'package:mkpos/controller/invoices_controller.dart';
import 'package:mkpos/utils/constants/colors.dart';
import 'package:mkpos/view/widgets/products/product_add_dialog_widget.dart';
class ProductsScreen extends StatefulWidget {
ProductsScreen({
super.key,
this.refreshPage,
});
final bool? refreshPage;
@override
State<ProductsScreen> createState() => _ProductsScreenState();
}
class _ProductsScreenState extends State<ProductsScreen> {
final InvoicesController controller = Get.find();
final IndexController indexController = Get.find();
final ScrollController scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return GetBuilder<InvoicesController>(
initState: (state) => controller.setSkip(controller.products.length),
builder: (controller) => Scaffold(
appBar: AppBar(
title: Text('products'.tr),
actions: [
IconButton(
iconSize: 34,
color: Colors.green,
onPressed: () async {
return showDialog(
useSafeArea: false,
context: context,
builder: (BuildContext context) => Dialog.fullscreen(
insetAnimationCurve: Curves.easeInOut,
child: ProductAddDialogWidget(
controller: indexController,
title: 'categories',
redirectPage: 'productScreen',
),
),
);
},
icon: const Icon(
Icons.add_circle_rounded,
),
),
],
),
body: Ink(
color: Colors.white,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
//controller: TextEditingController(text: controller.searchTerm),
onChanged: (value) async {
await controller.setSearchTerm(value.toString());
await controller
.getProductsSearchTerm(controller.searchTerm);
},
decoration: InputDecoration(
contentPadding: REdgeInsets.all(5),
hintText: 'productSearch'.tr,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.sp),
),
),
),
),
Expanded(
child: controller.isLoadingProducts
? Center(
child: SizedBox(
height: MediaQuery.sizeOf(context).height - 250,
child: const SpinKitWaveSpinner(
color: Colors.blue,
),
),
)
: Container(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Expanded(
child: ListView.separated(
controller: scrollController,
padding: REdgeInsets.symmetric(
horizontal: 2.sp,
vertical: 1.sp,
),
scrollDirection: Axis.vertical,
separatorBuilder: (context, index) =>
const Divider(height: 0.2),
itemCount: controller.products.length,
itemBuilder: (context, index) {
return ListTile(
contentPadding:
EdgeInsets.only(left: 20, right: 10),
dense: true,
leading: IconButton(
icon: const Icon(
Icons.delete,
size: 24,
color: Colors.red,
),
onPressed: () async {
await controller.deleteProduct(
controller.products[index].Id);
},
),
title: Text(
controller.products[index].title,
style: const TextStyle(
color: MKPColors.darkGrey,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
controller.products[index].price
.toStringAsFixed(2) +
' CHF',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 12,
),
),
trailing: IconButton(
iconSize: 24,
color: Colors.blue,
onPressed: () async {
await controller.addProductToCart(
controller.products[index].Id);
},
icon: const Icon(
Icons.edit,
),
),
);
},
),
),
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: SizedBox(
height: 35,
//width: MediaQuery.of(context).size.width / 2.5,
child: controller.skip ==
controller.products.length
? TextButton.icon(
icon: const Icon(
Icons.check,
color: Colors.white,
size: 20,
),
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
),
backgroundColor: Colors.green,
),
onPressed: () async {},
label: Text(
'allLoadedMore'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 13),
),
)
: TextButton.icon(
icon: const Icon(
Icons.add,
color: Colors.white,
size: 20,
),
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
),
backgroundColor:
Theme.of(context).primaryColor,
),
onPressed: () async {
await controller.setSkip(
controller.products.length);
controller.getProducts();
scrollController.animateTo(
scrollController
.position.extentTotal,
duration: const Duration(
milliseconds: 300),
curve: Curves.easeOut,
);
},
label: Text(
'loadMore'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 13),
),
),
),
),
],
),
),
),
],
)),
),
);
}
}
I’m working on a Flutter application where I have a ListView.separated widget. When I click the "Load More" button, I fetch new data from my controller and append it to the list. However, I want the ListView to automatically scroll down to the bottom to show the newly loaded data.
Here is the code I’m currently using:
onPressed: () async {
await controller.setSkip(controller.products.length);
controller.getProducts();
scrollController.animateTo(
scrollController.position.extentTotal,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
Despite using this code, the ListView doesn’t scroll to the bottom as expected. I’ve tried various approaches, but none seem to work. What alternative methods can I try to ensure the ListView scrolls to the bottom when new data is added?
Thank you for your help!
2
Answers
I figured out what the issue was. The problem occurred because I wasn’t using async and await properly in my code. Here’s the corrected code that solved the issue:
By properly awaiting the getProducts() call, the ListView now correctly scrolls to the bottom after new data is loaded.
Scroll to the end of the list through max scroll extent:
for more about scroll controller