Generate Paging
Generate Controller and Widgets depending on animated_infinite_scroll_pagination package.CLI works on generate Controller which responsible for fetching data from repository, with ability to link it with viewModel.xflutter_cli generate paging
Generated Code:
PagingController:
CLI will generate a PagingControllerwhich responsible for fetching data from repository and hold the fetched in observable data holder like flutterx_live_data.@LazySingleton(scope: 'productsPaging') // be sure to register 'productsPaging' before using the controller
class ProductsPagingController with AnimatedInfinitePaginationController<Product> {
final ProductsRepository _repository;
ProductsPagingController(this._repository);
/// decide whether two object represent the same Item
@override
bool areItemsTheSame(Product oldItem, Product newItem) {
return oldItem.id == newItem.id;
}
/// fetch data from repository and emit new state
@override
Future<void> fetchData(int page) async {
// emit loading
emitState(const PaginationLoadingState());
// fetch data from server
try {
await for (final response in _repository.findAll(page)) {
final result = response.data;
if (result?.data != null && response.isSuccess) {
// emit fetched data
emitState(PaginationSuccessState(result!.data!.toList(), cached: result.cached));
setTotal(result.total ?? 0);
} else {
// emit error
emitState(const PaginationErrorState());
}
}
} catch (error) {
// emit error
emitState(const PaginationErrorState());
if (kDebugMode) print(error);
}
}
/// cancel all pending requests
void dispose() {
_repository.dispose();
}
}
Fetch Data from Controller:
to make pagingController fetch the first chuck of data:@LazySingleton(scope: 'productsList')
class ProductsListViewModel extends BaseViewModel {
final params = ProductsListParams();
final ProductsPagingController _productsPagingController;
ProductsListViewModel(this._productsPagingController);
@override
void onReady() {
super.onReady();
// fetch first products chuck.
_productsPagingController.fetchNewChunk();
}
@override
void onDispose() {
// called immediately before the widget is disposed
_productsPagingController.dispose();
super.onDispose();
}
}
Register Controller Scope:
Generated PagingController depends on Scoped Singletons, So make sure to register the PagingController and Repository Scopes inside LifecycleOwner of screen before using the controller.class _ProductsListScreenState extends State<ProductsListScreen> with LifecycleOwner<ProductsListScreen, ProductsListViewModel>, ObserverMixin {
@override
DiScope get diScope => DiScope(
name: 'productsList', // (viewModel) scope
factory: getIt.initProductsListScope,
dependencies: [
// be sure to make the 'initProductsScope' before 'initProductsPagingScope'
DiScope(
name: 'products', // (repository and data-source) scope
factory: getIt.initProductsScope,
),
DiScope(
name: 'productsPaging', // (paging controller) scope
factory: getIt.initProductsPagingScope,
),
],
);
}
Products List:
CLI will a List Widget which will display the fetched items from PagingController depending on infinite-scroll-viewclass ProductsListWidget extends StatelessWidget {
final ProductsPagingController controller;
const ProductsListWidget({required this.controller, super.key});
@override
Widget build(BuildContext context) {
return InfiniteScrollView<Product, ProductsPagingController>(
controller: controller,
loadingWidget: const ProductRedactedWidget(),
itemBuilder: (BuildContext context, Product item, int index) => ProductCardWidget(item),
);
}
}
Product Card:
CLI will a Card Widget which will render the item Ui elementsclass ProductCardWidget extends StatelessWidget {
final Product product;
const ProductCardWidget(this.product, {super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.all(10),
child: const SizedBox(),
);
}
}
Product Loading:
CLI will a Redacted Widget which will be displayed when fetching the first pageclass ProductRedactedWidget extends StatelessWidget {
final int itemCount;
const ProductRedactedWidget({this.itemCount = 6, super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) => Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
child: const Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Test Item'),
SizedBox(height: 8),
Text('Test Item sub-title', style: TextStyle(fontSize: 12)),
],
).redacted(context: context, redact: true),
),
),
);
}
}
Implementing Paging List Widget:
Wrap the generated Products List in your screen:class ProductsListMobileScreen extends StatelessWidget {
const ProductsListMobileScreen({super.key});
@override
Widget build(BuildContext context) {
return ProductsListWidget(
controller: GetIt.instance<ProductsPagingController>(), // the same registered instance in the scope
);
}
}
Result:
CLI provides two options to create the Flutter application Layer-Based Architecture and Feature-Based ArchitectureLayer-Based Architecture:
- xflutter_cli_test_application
- android
- assets
- ios
- lib
- mediators
- paging
- ui
- screens
- products
- products_list
- mobile
- viewmodels
- widgets
Feature-Based Architecture:
- xflutter_cli_modules_application
- android
- assets
- ios
- lib
- modules
- products
- mediators
- paging
- presentation
- screens
- products_list
- mobile
- viewmodels
- widgets
Usage:
Name | Type | Description | Example |
---|---|---|---|
src | option | specify one of the modules to execute command in (monorepo workspace or standalone modular-app) | |
entity-name | option | specify entity className | |
source | option | import file from specific module, this is helpful when you have multiple files with same name and one of them should be imported in the generated file, so you need to tell CLI which one should be imported | filename.dart:my_app1 |
sources | option | import file from multiple modules, this is helpful when you have multiple files with same name and many of them of them should be imported in the generated file, so you need to tell CLI which files should be imported | models.dart:my_package1, models.dart:my_package2 |