Skip to content

Instantly share code, notes, and snippets.

@Roaa94
Last active September 25, 2023 15:47
Show Gist options
  • Save Roaa94/4e6febdfc732e70572b3fbd53cfeea4e to your computer and use it in GitHub Desktop.
Save Roaa94/4e6febdfc732e70572b3fbd53cfeea4e to your computer and use it in GitHub Desktop.
Infinite Scrolling with Riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
// Disclaimer: This uses the "The Movie Database API (TMDB)"
// https://developers.themoviedb.org/3/getting-started
// With this endpoint:
// https://developers.themoviedb.org/3/people/get-popular-people
/// The FutureProvider that does the fetching of the paginated list of people
final paginatedPopularPeopleProvider =
FutureProvider.family<PaginatedResponse<Person>, int>(
(ref, int pageIndex) async {
final peopleRepository = ref.watch(peopleRepositoryProvider);
// The API request:
return await peopleRepository.getPopularPeople(page: pageIndex + 1);
},
);
/// The provider that has the value of the total count of the list items
///
/// The [PaginatedResponse] class contains information about the total number of
/// pages and the total results in all pages along with a list of the provided type
///
/// An example response from the API for any page looks like this:
/// {
/// "page": 1,
/// "results": [], // list of 20 items
/// "total_pages": 500,
/// "total_results": 10000 // Value taken by this provider
/// }
final popularPeopleCountProvider = Provider<AsyncValue<int>>((ref) {
return ref.watch(paginatedPopularPeopleProvider(0)).whenData(
(PaginatedResponse<Person> pageData) => pageData.totalResults,
);
});
/// The provider that provides the Person data for each list item
///
/// Initially it throws an UnimplementedError because we won't use it before overriding it
final currentPopularPersonProvider = Provider<AsyncValue<Person>>((ref) {
throw UnimplementedError();
});
class PopularPeopleList extends ConsumerWidget {
const PopularPeopleList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final popularPeopleCount = ref.watch(popularPeopleCountProvider);
// The ListView's count is from the popularPeopleCountProvider which
// by watching it here, causes the first fetch with a page index of 0
return popularPeopleCount.when(
loading: () => const CircularProgressIndicator(),
data: (int count) {
return ListView.builder(
itemCount: count,
itemBuilder: (context, index) {
// At this point the paginatedPopularPeopleProvider stores the values of the
// list items of at least the first page
//
// (index ~/ 20): Performing a truncating division of the list item index by the number of
// items per page gives us the value of the current page that we then access using the
// family modifier of the paginatedPopularPeopleProvider provider
// This way calling 21 ~/ 20 = 1 will fetch the second page,
// and 41 ~/ 20 = 2 will fetch the 3rd page, and so on.
final AsyncValue<Person> currentPopularPersonFromIndex = ref
.watch(paginatedPopularPeopleProvider(index ~/ 20))
.whenData((pageData) => pageData.results[index % 20]);
return ProviderScope(
overrides: [
// Override the Unimplemented provider
currentPopularPersonProvider.overrideWithValue(currentPopularPersonFromIndex)
],
child: const PopularPersonListItem(),
);
},
);
},
// Handle error
error: (_, __) => const Icon(Icons.error),
);
}
}
class PopularPersonListItem extends ConsumerWidget {
const PopularPersonListItem({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
// Here we don't need to do anything but listen to the currentPopularPersonProvider's
// AsyncValue that was overridden in the ListView's builder
final AsyncValue<Person> personAsync = ref.watch(currentPopularPersonProvider);
return Container(
child: personAsync.when(
data: (Person person) => Container(/* ... */), // List item content
loading: () => const CircularProgressIndicator(), // Handle loading
error: (_, __) => const Icon(Icons.error), // Handle Error
),
);
}
}
final httpClientProvider = Provider<Dio>((ref) => Dio());
final peopleRepositoryProvider = Provider<PeopleRepository>(
(ref) {
final httpClient = ref.watch(httpClientProvider);
return HttpPeopleRepository(httpClient);
},
);
abstract class PeopleRepository {
Future<PaginatedResponse<Person>> getPopularPeople({
int page = 1,
});
}
class HttpPeopleRepository implements PeopleRepository {
final Dio httpClient;
HttpPeopleRepository(this.httpClient);
@override
Future<PaginatedResponse<Person>> getPopularPeople({
int page = 1,
}) async {
final response = await httpClient.get(
'/person/popular',
queryParameters: {
'page': page,
'api_key': const String.fromEnvironment('API_KEY'),
},
);
return PaginatedResponse.fromJson(
response.data,
results: List<Person>.from(
response.data['results'].map((x) => Person.fromJson(x)),
),
);
}
}
class PaginatedResponse<T> {
final int page;
final List<T> results;
final int totalPages;
final int totalResults;
PaginatedResponse({
this.page = 1,
this.results = const [],
this.totalPages = 1,
this.totalResults = 1,
});
factory PaginatedResponse.fromJson(
Map<String, dynamic> json, {
required List<T> results,
}) {
return PaginatedResponse<T>(
page: json['page'],
results: results,
totalPages: json['total_pages'],
totalResults: json['total_results'],
);
}
}
class Person {
final int id;
final String name;
const Person({
required this.id,
required this.name,
});
factory Person.fromJson(Map<String, dynamic> json) {
return Person(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
};
}
}
@shahmirzali49
Copy link

@Roaa94 Actually it's not a good method for error handling. or what's your method for showing the user error page?

I mean this problem.

WhatsApp Image 2022-09-02 at 20 35 56

@apierraf
Copy link

It does not page me, it repeats the same object, it does not increase the page index

@kivocsa99
Copy link

@Shahmirzali-Huseynov so what do u suggest doing with riverpod , hooks for implementing this :

iam really not able to achieve infinite scroll view

import 'package:auto_route/auto_route.dart';
import 'package:bld/application/provider/orders.repository.provider.dart';
import 'package:bld/constatns.dart';
import 'package:bld/domain/categoryandproductsmodel/categoryandproductsmodel.dart';
import 'package:bld/presentation/components/error_widget.dart';
import 'package:bld/routes/app_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:transparent_image/transparent_image.dart';
import '../components/searchbar.dart';

//supplier image
@RoutePage()
class CategoryScreen extends HookConsumerWidget {
  final String? categoryName;
  final String? categoryId;
  const CategoryScreen({this.categoryId, super.key, this.categoryName});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final categoryItems = ref.watch(searchItemsProvider(
        supplierid: "", categoryid: categoryId!, title: ""));
    final itemsScrollController = useScrollController();
    useEffect(() {
      print("hey");
      () => itemsScrollController
        ..addListener(() => itemsScrollController.position.maxScrollExtent ==
                itemsScrollController.offset
            ?
            // User has scrolled to the end
            print("hello")
            : print("no"));
      print("object");

      return () => itemsScrollController.removeListener(() {});
    });
    return SafeArea(
      child: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraints) {
            return Container(
              width: constraints.maxWidth,
              height: constraints.maxHeight,
              color: const Color(0xffF2F2F2),
              child: categoryItems.when(
                  data: (data) => data.fold(
                          (l) => SomeThingWentWrongErrorWidget(
                                refresh: () => ref.watch(searchItemsProvider(
                                    supplierid: "",
                                    categoryid: categoryId!,
                                    title: "")),
                              ), (r) {
                        List<CategoryAndProductsModel> categories = r;

                        // WidgetsBinding.instance.addPostFrameCallback((_) async {
                        //   // Check if the list is scrolled to the maximum extent after initial data is available
                        //   if (itemsScrollController.position.maxScrollExtent ==
                        //       itemsScrollController.offset) {
                        //     return await ref
                        //         .watch(searchItemsProvider(
                        //             nextUrl: categorybox
                        //                 .get("categoryproductnexturl"),
                        //             supplierid: "",
                        //             categoryid: "",
                        //             title: ""))
                        //         .when(
                        //             data: (data) =>
                        //                 data.fold((l) => print(l), (r) {
                        //                   List<CategoryAndProductsModel>
                        //                       newproducts = r;
                        //                   return print(newproducts.length);
                        //                 }),
                        //             error: (error, stackTrace) => null,
                        //             loading: () => null);
                        //   }
                        // });

                        return categories.isEmpty
                            ? const Center(
                                child: Text(
                                  "There are no products in this category",
                                  style: TextStyle(
                                      fontSize: 16,
                                      fontWeight: FontWeight.bold),
                                ),
                              )
                            : Padding(
                                padding: const EdgeInsets.only(
                                    left: 25.0, right: 25.0),
                                child: Column(
                                  children: [
                                    const SizedBox(
                                      height: 10,
                                    ),
                                    SizedBox(
                                      width: double.infinity,
                                      height: 62,
                                      child: Row(
                                        mainAxisAlignment:
                                            MainAxisAlignment.spaceBetween,
                                        children: [
                                          GestureDetector(
                                            onTap: () => context.router.pop(),
                                            child: Container(
                                              decoration: const BoxDecoration(
                                                  color: Colors.white,
                                                  borderRadius:
                                                      BorderRadius.all(
                                                          Radius.circular(
                                                              15.0))),
                                              width: 42,
                                              height: 42,
                                              child: Center(
                                                child: Image.asset(
                                                    "assets/back.png"),
                                              ),
                                            ),
                                          ),
                                          Center(
                                            child: Text(
                                              categoryName!,
                                              style: const TextStyle(
                                                  fontWeight: FontWeight.bold,
                                                  fontSize: 20),
                                            ),
                                          ),
                                          const SizedBox(
                                            width: 42,
                                            height: 42,
                                          ),
                                        ],
                                      ),
                                    ),
                                    const SizedBox(
                                      height: 20,
                                    ),
                                    const SizedBox(
                                      height: 46,
                                      child: OrderSearchBar(),
                                    ),
                                    const SizedBox(
                                      height: 20,
                                    ),
                                    Expanded(
                                      child: Column(
                                        children: [
                                          Align(
                                            alignment: Alignment.centerLeft,
                                            child: Text(
                                              "${categorybox.get("categoryproductcount")} Results",
                                              style: const TextStyle(
                                                  color: Colors.black,
                                                  fontSize: 16,
                                                  fontWeight: FontWeight.bold),
                                            ),
                                          ),
                                          const SizedBox(
                                            height: 10,
                                          ),
                                          Expanded(
                                            child: ListView.separated(
                                              separatorBuilder:
                                                  (context, index) {
                                                return const SizedBox(
                                                  height: 10,
                                                );
                                              },
                                              itemCount: categories.length,
                                              controller: itemsScrollController,
                                              itemBuilder: (context, index) {
                                                final CategoryAndProductsModel
                                                    category =
                                                    categories[index];
                                                return GestureDetector(
                                                  onTap: () => context.router
                                                      .push(ProductRoute(
                                                          product: category)),
                                                  child: Container(
                                                    decoration: BoxDecoration(
                                                        borderRadius:
                                                            BorderRadius
                                                                .circular(25),
                                                        color: Colors.white),
                                                    width: double.infinity,
                                                    height: 85,
                                                    child: Padding(
                                                      padding:
                                                          const EdgeInsets.all(
                                                              10.0),
                                                      child: Row(
                                                          mainAxisAlignment:
                                                              MainAxisAlignment
                                                                  .spaceBetween,
                                                          children: [
                                                            Row(
                                                              children: [
                                                                Container(
                                                                    width: 70,
                                                                    height: 70,
                                                                    padding:
                                                                        const EdgeInsets.all(
                                                                            5),
                                                                    decoration: BoxDecoration(
                                                                        color: const Color(
                                                                            0xFFE2E2E2),
                                                                        borderRadius:
                                                                            BorderRadius.circular(
                                                                                15)),
                                                                    child: FadeInImage
                                                                        .memoryNetwork(
                                                                      placeholder:
                                                                          kTransparentImage,
                                                                      image:
                                                                          "$storageUrl${category.product!.image}",
                                                                      fit: BoxFit
                                                                          .contain,
                                                                    )),
                                                                const SizedBox(
                                                                  width: 10,
                                                                ),
                                                                Column(
                                                                  crossAxisAlignment:
                                                                      CrossAxisAlignment
                                                                          .start,
                                                                  mainAxisAlignment:
                                                                      MainAxisAlignment
                                                                          .center,
                                                                  children: [
                                                                    Text(
                                                                      "${category.product!.name}",
                                                                      style: const TextStyle(
                                                                          color: Colors
                                                                              .black,
                                                                          fontSize:
                                                                              16,
                                                                          fontWeight:
                                                                              FontWeight.normal),
                                                                    ),
                                                                    const SizedBox(
                                                                      height: 5,
                                                                    ),
                                                                    Text(
                                                                      "JOD ${category.price}",
                                                                      style: const TextStyle(
                                                                          fontSize:
                                                                              16,
                                                                          color:
                                                                              Color(0xff3B788B)),
                                                                    )
                                                                  ],
                                                                )
                                                              ],
                                                            ),
                                                            Row(
                                                              children: [
                                                                const VerticalDivider(
                                                                  width: 1,
                                                                  color: Color(
                                                                      0xffDEF0F5),
                                                                ),
                                                                const SizedBox(
                                                                  width: 5,
                                                                ),
                                                                Container(
                                                                  height: 40,
                                                                  width: 60,
                                                                  padding:
                                                                      const EdgeInsets
                                                                          .all(5),
                                                                  color: Colors
                                                                      .transparent,
                                                                  child: FadeInImage
                                                                      .memoryNetwork(
                                                                    placeholder:
                                                                        kTransparentImage,
                                                                    image:
                                                                        "$storageUrl${category.supplier!.image}",
                                                                    fit: BoxFit
                                                                        .cover,
                                                                  ),
                                                                )
                                                              ],
                                                            )
                                                          ]),
                                                    ),
                                                  ),
                                                );
                                              },
                                            ),
                                          ),
                                        ],
                                      ),
                                    ),
                                    const SizedBox(
                                      height: 15,
                                    )
                                  ],
                                ),
                              );
                      }),
                  error: (error, stackTrace) => SomeThingWentWrongErrorWidget(
                        refresh: () => ref.watch(searchItemsProvider(
                                supplierid: "",
                                categoryid: categoryId!,
                                title: "")
                            .future),
                      ),
                  loading: () => const Center(
                        child: SpinKitCubeGrid(
                          color: Colors.blue,
                        ),
                      )),
            );
          },
        ),
      ),
    );
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment