I am making an infinite scroll view in flutter with bloc state management, the issue is that my state is not updating when I go to the bottom of the screen, I noticed that when I call the ..add(FetchAllProductEvent) function in the blocprovider it is fetching me the initial products, but when i call it in the onscroll function it is not updating the ui, as I can also see the logs that the onscroll function is called properly and the API calls have been made. But my blocbuilder is not reacting to the states. also, when I call it in, initstate state is not even working for initial products.
import 'dart:async';
import 'dart:developer';
import 'package:boteek/src/blocs/category/category_bloc.dart';
import 'package:boteek/src/blocs/product/product_bloc.dart';
import 'package:boteek/src/data/product_repository.dart';
import 'package:boteek/src/models/product_model.dart';
import 'package:boteek/src/presentation/constants/color.dart';
import 'package:boteek/src/presentation/screens/product_screen.dart';
import 'package:boteek/src/presentation/widgets/buttons/k_button.dart';
import 'package:boteek/src/presentation/widgets/misc/circular_progress_indicator.dart';
import 'package:boteek/src/presentation/widgets/products/product_card.dart';
import 'package:boteek/src/presentation/widgets/shimmer/home_shimmer.dart';
import 'package:boteek/src/presentation/widgets/shimmer/product_shimmer.dart';
import 'package:boteek/src/presentation/widgets/textfields/normal_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String selectedCategory = "All";
List<String> categories = ["All"];
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
BlocProvider.of<ProductBloc>(context).
add(FetchAllProductEvent());
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels != 0 && _scrollController.position.atEdge) {
BlocProvider.of<ProductBloc>(context).add(FetchAllProductEvent());
}
}
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
body: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => CategoryBloc(productRepository: ProductRepository())
..add(FetchAllCategoriesEvent()),
),
BlocProvider(
create: (context) => ProductBloc()
// ..add(FetchAllProductEvent()),
),
],
child: Column(
children: [
BlocConsumer<CategoryBloc, CategoryState>(
listener: (context, state) {
if (state is AllCategoriesSuccessState) {
setState(() {
categories.addAll(state.categories.map((category) => category.categoryName));
});
}
},
builder: (context, state) {
return const SizedBox();
},
),
Expanded(
child: BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
log("the state is $state");
if (state is AllProductFailureState) {
return Center(child: Text(state.error));
}
if (state is AllProductLoadingState && state.isFirstFetch) {
return _buildLoading();
}
List<Product> _product = [];
bool isLoading = false;
if (state is AllProductLoadingState) {
_product = state.oldProducts;
isLoading = true;
} else if (state is AllProductSuccessState) {
_product = state.products;
}
final filteredProducts = selectedCategory == "All"
? _product
: _product.where((product) => product.categories.contains(selectedCategory)).toList();
return CustomScrollView(
controller: _scrollController,
slivers: [
SliverList(
delegate: SliverChildListDelegate([
banner(size: size),
const SizedBox(height: 10),
searchBar(size: size),
const SizedBox(height: 10),
categoryTab(size: size),
const SizedBox(height: 10),
SizedBox(
height: size.height * 0.05,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
selectedCategory = categories[index];
});
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: size.width * 0.04,
vertical: size.height * 0.01),
margin: EdgeInsets.symmetric(
horizontal: size.width * 0.01),
decoration: BoxDecoration(
color: selectedCategory == categories[index]
? Colors.white
: Colors.black,
borderRadius: BorderRadius.circular(12),
border: selectedCategory != categories[index]
? Border.all(color: Colors.white)
: null),
child: Center(
child: Text(
categories[index],
style: TextStyle(
color: selectedCategory == categories[index]
? Colors.black
: Colors.white,
),
),
),
),
);
},
),
),
const SizedBox(height: 10),
]),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (size.width < 600) ? 2 : 4,
crossAxisSpacing: size.width * 0.02,
mainAxisSpacing: size.height * 0.02,
childAspectRatio: (size.width < 600) ? 0.55 : 0.75,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index < filteredProducts.length) {
final product = filteredProducts[index];
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, ProductScreen.routeName,
arguments: {'productId': product.productId});
},
child: KProductCard(
imageUrl: product.productUrl,
discount: product.discount,
price: product.amount,
isLiked: true,
productTitle: "$index ${product.productName}",
),
);
} else {
Timer(const Duration(milliseconds: 30), () {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
});
return const SizedBox(
height: 100,
child: Center(child: CircularLoader()),
);
}
},
childCount: filteredProducts.length + (isLoading ? 1 : 0),
),
),
],
);
},
),
),
],
),
),
);
}
Widget _buildLoading() {
return SingleChildScrollView(
child: Column(
children: [
HomeShimmer(),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: GridView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 7.0,
mainAxisSpacing: 0.0,
childAspectRatio: 0.59,
),
itemCount: 10,
itemBuilder: (context, index) {
return ProductShimmer();
},
),
),
],
),
);
}
}
class categoryTab extends StatelessWidget {
const categoryTab({
super.key,
required this.size,
});
final Size size;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: size.width * 0.02),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Categories"),
InkWell(
child: Text(
"View All",
style: TextStyle(color: AppColour.grey),
),
)
],
),
);
}
}
class searchBar extends StatelessWidget {
const searchBar({
super.key,
required this.size,
});
final Size size;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: size.width * 0.02),
child: KTextField(
prefixIcon: Icon(Icons.search),
hintText: "What product you're looking for?",
suffixIcon: Icon(Icons.filter_alt_outlined),
),
);
}
}
class banner extends StatelessWidget {
const banner({
super.key,
required this.size,
});
final Size size;
@override
Widget build(BuildContext context) {
return Container(
height: size.height * 0.2,
width: size.width,
decoration: BoxDecoration(
color: AppColour.categoryButtonDark,
borderRadius: BorderRadius.circular(15),
),
child: Stack(
children: [
Positioned(
top: 0,
left: -50,
child: Transform.rotate(
angle: 180 * 3.1415927 / 180,
child: Container(
height: 100,
width: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
"assets/images/ring_bottom_right.png"),
),
),
),
),
),
Positioned(
bottom: -10,
right: -32,
child: Container(
height: 100,
width: 200,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
"assets/images/ring_bottom_right.png"),
),
),
),
),
// Text and button
Positioned(
left: 20,
top: 30,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Discover Products",
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 10),
KButton(
width: size.width * 0.3,
height: size.height * 0.021,
buttonText: "Shop Now",
onPressed: () {},
)
],
),
),
],
),
);
}
}
product_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:boteek/src/data/product_repository.dart';
import 'package:boteek/src/models/category_model.dart';
import 'package:boteek/src/models/product_description_model.dart';
import 'package:boteek/src/models/product_model.dart';
import 'package:meta/meta.dart';
part 'product_event.dart';
part 'product_state.dart';
class ProductBloc extends Bloc<ProductEvent, ProductState> {
int page = 1;
ProductRepository productRepository = ProductRepository();
ProductBloc() : super(ProductInitial()) {
on<FetchNewInProductsEvent>(_fetchNewInProduct);
on<FetchAllProductEvent>(_fetchAllProduct);
on<FetchAllCategoryEvent>(_fetchAllCategories);
on<FetchTheProductEvent>(_fetchTheProduct);
// on<FetchMoreProductEvent>(_onFetchMoreProducts);
}
FutureOr<void> _fetchAllProduct(FetchAllProductEvent event, Emitter<ProductState> emit) async {
try{
if(state is AllProductLoadingState) return;
final currentState = state;
var oldProducts = <Product>[];
if(currentState is AllProductSuccessState){
oldProducts = currentState.products;
}
emit(AllProductLoadingState(oldProducts,isFirstFetch: page == 1));
var newProducts = await productRepository.getAllProduct(page);
print("new products $newProducts");
page = page + 1;
final products = (state as AllProductLoadingState).oldProducts;
products.addAll(newProducts);
print("new product size ${products.length}");
emit(AllProductSuccessState(products));
} catch(e){
emit(AllProductFailureState(e.toString()));
}
}
Future<void> _fetchNewInProduct(event, emit) async {
if (event is FetchNewInProductsEvent) {
emit(NewInProductLoadingState());
try {
List<Product> products = await productRepository.getNewInProduct();
emit(NewInProductSuccessState(products));
} catch (e) {
print(e.toString());
emit(NewInProductFailureState(e.toString()));
}
}
}
Future<void> _fetchAllCategories(event, emit) async {
if (event is FetchAllCategoryEvent) {
emit(AllCategoriesLoadingState());
try {
List<Category> categories = await productRepository.getAllCategories();
emit(AllCategoriesFetchedState(categories));
} catch (e) {
emit(AllCategoriesFailedState(e.toString()));
}
}
}
Future<void> _fetchTheProduct(event, emit) async {
if (event is FetchTheProductEvent) {
int productId = event.productId;
emit(ProductDetailsLoadingState());
try {
ProductDescription product =
await productRepository.getProduct(event.productId);
emit(ProductDetailsFetchedState(product));
} catch (e) {
emit(ProductDetailsFailedState(e.toString()));
}
}
}
}
product_state.dart
part of 'product_bloc.dart';
@immutable
sealed class ProductState {}
final class ProductInitial extends ProductState {}
class AllProductLoadingState extends ProductState {
final List<Product> oldProducts;
final bool isFirstFetch;
AllProductLoadingState(this.oldProducts,{this.isFirstFetch=false});
}
class AllProductSuccessState extends ProductState {
final List<Product> products;
AllProductSuccessState(this.products);
}
class AllProductFailureState extends ProductState {
final String error;
AllProductFailureState(this.error);
}
class NewInProductLoadingState extends ProductState {}
class NewInProductSuccessState extends ProductState {
final List<Product> products;
NewInProductSuccessState(this.products);
}
class NewInProductFailureState extends ProductState {
final String error;
NewInProductFailureState(this.error);
}
class AllCategoriesFetchedState extends ProductState {
final List<Category> categories;
AllCategoriesFetchedState(this.categories);
}
class AllCategoriesLoadingState extends ProductState {}
class AllCategoriesFailedState extends ProductState {
final String error;
AllCategoriesFailedState(this.error);
}
class ProductDetailsFetchedState extends ProductState {
final ProductDescription product;
ProductDetailsFetchedState(this.product);
}
class ProductDetailsLoadingState extends ProductState {}
class ProductDetailsFailedState extends ProductState {
final String error;
ProductDetailsFailedState(this.error);
}
class MoreProductLoadingState extends ProductState {}
class MoreProductSuccessState extends ProductState {
final List<Product> products;
MoreProductSuccessState({required this.products});
}