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

Generate new paging inside workspace

  • Make sure to run the command in the workspace root directory instead of (application or package) directory.
  • Use the src option to specify the (app or package) in which you want to generate the 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-view
class 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 elements
class 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 page
class 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 Architecture

Layer-Based Architecture:

  • xflutter_cli_test_application
    • android
    • assets
    • ios
    • lib
      • mediators
        • paging
          • products_paging_controller.dart
      • ui
        • screens
          • products
            • products_list
              • mobile
              • viewmodels
              • widgets
                • product_card_widget.dart
                • product_redacted_widget.dart
                • products_list_widget.dart
              • products_list_screen.dart
      • main.dart
    • analysis_options.yaml
    • build.yaml
    • flutter_launcher_icons.yaml
    • flutter_native_splash.yaml
    • pubspec.yaml
    • xflutter_cli.yaml

Feature-Based Architecture:

  • xflutter_cli_modules_application
    • android
    • assets
    • ios
    • lib
      • modules
        • products
          • mediators
            • paging
              • products_paging_controller.dart
          • presentation
            • screens
              • products_list
                • mobile
                • viewmodels
                • widgets
                  • product_card_widget.dart
                  • product_redacted_widget.dart
                  • products_list_widget.dart
                • products_list_screen.dart
      • main.dart
    • analysis_options.yaml
    • build.yaml
    • flutter_launcher_icons.yaml
    • flutter_native_splash.yaml
    • pubspec.yaml
    • xflutter_cli.yaml

Usage:

Name
Type
Description
Example
srcoptionspecify one of the modules to execute command in (monorepo workspace or standalone modular-app)
entity-nameoptionspecify entity className
sourceoptionimport 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 importedfilename.dart:my_app1
sourcesoptionimport 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 importedmodels.dart:my_package1, models.dart:my_package2