Flutter分页请求插件flutter_nestjs_paginate的使用

Flutter分页请求插件flutter_nestjs_paginate的使用

A library providing a base for building controllable paginated views powered by nestjs-paginate on the backend.

特性

  • <code>PaginationController</code> 提供了一个全面且反应式的接口来构建分页查询。它可以通过 <code>PaginateConfig</code> 进行配置,无论是硬编码还是直接从服务器接收,以增强客户端验证。<code>PaginateController</code> 支持所有 <code>nestjs-paginate</code> 的功能,除了 <code>select</code>,因为它会破坏大多数 Dart 中的 DTOs,但未来版本可能会改变。
  • <code>PaginatedView</code> 利用分页控制器和用户定义的小部件构建器来创建从服务器接收的数据的高度可定制化展示。
  • <code>Paginated&lt;TDto&gt;</code> 是一个包装器,用于从 <code>nestjs-paginate</code> 发送的分页 DTO,其中包括 <code>PaginatedMetadata</code> 和一个 <code>TDto</code> 的列表。

开始使用

要开始使用该包,请使用 pub 安装:

flutter pub add flutter_nestjs_paginate

这将安装最新兼容版本的包,并准备好使用。

最小使用示例

创建一个 <code>PaginateConfig</code>,可以通过硬编码或从服务器获取:

final configJson = await get&lt;Map&gt;('/paginate_config');
final config = PaginateConfig.fromJson(configJson);

// 或者

// 所有参数都是可选的
const config = PaginateConfig(
   defaultSortBy: {'name': SortOrder.asc},
   defaultLimit: 10,
   filterableColumns: {
     'name': {Eq, Ilike},
     'population': {Gt, Lt, Eq, Btw},
   },
   sortableColumns: {'name', 'population'},
);

要控制分页,使用 <code>PaginationController</code>。 创建它并提供已创建的配置:

// 在你的状态类中
final _controller = PaginationController(paginateConfig: config);

虽然配置参数是可选的,但传递它将启用过滤和排序验证。如果您想省略配置并禁用验证,请在控制器构造函数中传递 <code>validateColumns: false</code>。还有其他选项…

然后创建一个 <code>PaginatedView</code>

// 在 build() 方法中
return PaginatedView(
   // 提供控制器。其更新将使 <code>PaginatedView</code> 调用其 fetcher
   controller: _controller,
   // fetcher 用于向服务器发起分页请求
   // 它必须返回一个 <code>Paginated&lt;YourDto&gt;</code> - 更多信息见源码文档
   fetcher: (context, QueryParams params) { ... },
   // errorBuilder 将在发生错误时被调用,以可视化错误
   errorBuilder: (context, Object error) =&gt; YourErrorWidget(error),
   // loadingIndicator 将在数据获取期间构建
   loadingIndicator: (context) =&gt; YourProgressIndicator(),
   // viewBuilder 可视化从 fetcher 接收的 <code>TModel</code> 列表
   viewBuilder: (context, Paginated&lt;YourDto&gt; data) {
     return YourDataView(data.data);
   },
   // 您还可以为 fetch 开始结束事件提供监听器。它们将在 post-frame 回调中调用,因此您可以调用 setState
   onFetch: () { ... },
   onLoaded: (Paginated&lt;YourDto&gt; result) { ... },
);

现在,可以通过 <code>controller</code> 控制分页:

// 在按钮回调中

// 获取下一页
_controller.page++;

// 使用操作符 Btw 添加过滤器
_controller.addFilter(
   'date',
   const Btw('2023-12-20', '2023-12-26'),
);

// 同时执行多个操作
_controller.silently(
   notifyAfter: true,
   (controller) =&gt; controller
     ..page = 1
     ..clearFilters()
     ..addFilter('amount', const Gt(1000))
     ..addSort('amount', SortOrder.desc),
);

细节

约束

SortOrder

定义排序顺序的枚举:升序或降序:

enum SortOrder {
   asc,
   desc
}
FilterOperator

表示 <code>nestjs-paginate</code> 支持的过滤器的一组类:

  • $eq - <code>Eq</code>
  • $not - <code>Not</code>
  • $null - <code>Null</code>
  • $in - <code>In</code>
  • $gt, $gte - <code>Gt</code> - 对于 $gte,在构造函数中提供 <code>orEquals: true</code>
  • $lt, $lte - <code>Lt</code> - 对于 $lte,在构造函数中提供 <code>orEquals: true</code>
  • $btw - <code>Btw</code>
  • $ilike - <code>Ilike</code>
  • $sw - <code>Sw</code>
  • $contains - <code>Contains</code>

PaginationController

此类提供了用于控制分页的反应式接口。

它具有与 <code>nestjs-paginate</code> 接受的查询参数名称相同的字段,并可以使用 <code>PaginateConfig</code> 对象进行配置以启用客户端验证。

验证

分页控制器支持列验证,默认启用 - <code>validateColumns</code>

它也可以强制执行以防止生产中的意外行为(默认禁用)- <code>strictValidation</code> - 它将抛出有意义的消息的 <code>StateError</code> 来帮助您找到问题。

当启用验证时,它将使用提供的 <code>PaginateConfig</code> 中的值(仅当提供时)。

PaginateConfig

一个类,复制了 <code>nestjs-paginate</code><code>PaginateConfig</code> 结构,省略了数据库和后端特定的值,可以传递给 <code>PaginationController</code> 构造函数。然后它可以由开发人员和控制器用来启用验证。

它包含以下字段:

  • Set&lt;String&gt; <code>sortableColumns</code> - 控制器在 <code>addSort</code> 中接受的列名。默认值:{} - 不允许任何列排序。
  • Map&lt;String, Set&lt;Type&gt;&gt; <code>filterableColumns</code> - 一个映射,其中包含后端接受的列名用于过滤到它们允许的 <code>FilterOperator</code> 类型的集合。默认值:{} - 不允许任何列过滤。
  • int <code>maxLimit</code> - 服务器上配置的最大限制。默认值:100
  • int <code>defaultLimit</code> - 默认设置的限制值。默认值:20
  • Map&lt;String, SortOrder&gt; <code>defaultSortBy</code> - 默认排序查询。

<code>PaginateConfig</code> 具有一个 <code>fromJson</code> 工厂,支持直接反序列化 <code>nestjs-paginate</code> 配置语法。

确保不要发送您在 Flutter 应用程序中不需要的后端特定值,以避免潜在的系统详细信息披露。

注意,默认情况下不允许任何列用于排序和过滤,因此您应考虑提供 <code>paginateConfig</code> 或在控制器中设置 <code>validateColumns</code>false

分页

要控制分页,请使用以下方法:

  • get/set int <strong><code>page</code></strong> - 请求的分页数据的页码。默认值:1
  • get/set int <strong><code>limit</code></strong> - 每页的最大条目数。默认值:20。 如果设置为大于 <code>PaginateConfig.maxLimit</code> 的值,则将被钳位到 <code>maxLimit</code>
  • get/set Object? <strong><code>search</code></strong> - 要发送的搜索查询。默认值:null。 注意,搜索对象必须以有意义的方式使用 <code>toString</code> 方法进行字符串序列化,以便正确地被您的服务器接收。

如果更改这些字段中的任何一个,控制器将通知其监听器(除非在 <code>silently</code> 函数内部更改)。

过滤器

要控制过滤器,请使用控制器的以下成员:

  • get Map&lt;String, Set&lt;FilterOperator&gt;&gt; <strong><code>filters</code></strong> - 当前应用的过滤器的不可修改视图。<code>nestjs-paginate</code> 支持单个列上的多个过滤器。映射结构如下:
{
   'column': { FilterOperator, FilterOperator, ... },
   ...
}
  • <code>addFilter(String field, FilterOperator operator)</code> - 向查询添加指定字段和 <code>FilterOperator</code> 的过滤器。如果过滤器集发生变化,通知监听器。
  • <code>removeFilter(String field, [FilterOperator? operator])</code> - 如果未给出操作符,则删除指定字段的所有过滤器。否则,从字段中移除指定操作符的过滤器。
  • <code>clearFilters()</code> - 从查询中删除所有过滤器。如果过滤器集不为空,通知监听器。
排序

要控制排序,请使用以下成员:

  • get Map&lt;String, SortOrder&gt; <strong><code>sorts</code></strong> - 当前应用的排序的不可修改视图。映射结构如下:
{
   'column': SortOrder,
   ...
}
  • <code>addSort(String field, SortOrder order)</code> - 更新查询以包括指定字段按指定顺序排序。如果排序集发生变化,通知监听器。
  • <code>removeSort(String field)</code> - 删除指定字段的排序查询。如果存在这样的查询,通知监听器。
  • <code>clearFilters()</code> - 从查询中删除所有排序。如果排序集不为空,通知监听器。
多个操作

默认情况下,更改控制器的任何字段都会触发通知并使 <code>PaginatedView</code> 调用其 fetcher。但是,如果您需要同时更改多个参数且不需要通知监听器,可以使用 <code>silently(Function(PaginationController) fn, {bool notifyAfter = false})</code>

有关更多信息,请参阅源代码中的文档。

Paginated<TDto>

这是从分页端点接收响应的通用包装器。

<code>nestjs-paginate</code><code>PaginateConfig</code> 匹配,它包含 <code>data</code> - 您的 DTO 列表,以及 <code>meta</code> - <code>PaginatedMetadata</code>

可以使用 <code>Paginated.fromJson&lt;TDto&gt;(json, decoder)</code> 反序列化,其中 <code>decoder</code> 是一个函数,接受一个 <code>Map&lt;String, dynamic&gt;</code> 并返回您的 DTO:

final json = await client.get&lt;Map&lt;String, dynamic&gt;&gt;('/paginated_collection');

final paginatedRes = Paginated.fromJson(json, YourDto.fromJson);

贡献

如果您希望为本包做出贡献,您可以:

所有贡献都欢迎。


示例代码

import 'package:flutter/material.dart' hide ErrorWidget;
import 'package:flutter_nestjs_paginate/flutter_nestjs_paginate.dart';
import 'package:flutter_nestjs_paginate_example/cities_view.dart';

import 'error_widget.dart';
import 'fetcher.dart';

void main() =&gt; runApp(const FlutterNestjsPaginateExample());

class FlutterNestjsPaginateExample extends StatelessWidget {
  const FlutterNestjsPaginateExample({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) =&gt;
      const MaterialApp(home: PaginatedPage());
}

class PaginatedPage extends StatefulWidget {
  const PaginatedPage({super.key});

  [@override](/user/override)
  State&lt;PaginatedPage&gt; createState() =&gt; _PaginatedPageState();
}

class _PaginatedPageState extends State&lt;PaginatedPage&gt; {
  static const PaginateConfig _config = PaginateConfig(defaultLimit: 3);

  final PaginationController _controller =
      PaginationController(paginateConfig: _config);

  PaginatedMetadata? _metadata;

  // fetcher 将在加载时被调用,所以控制将被锁定
  bool _areControlsLocked = true;

  [@override](/user/override)
  Widget build(BuildContext context) {
    final meta = _metadata;

    return Scaffold(
      body: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          PaginatedView(
            // 提供控制器以控制分页
            controller: _controller,
            // fetcher 用于向服务器发起分页请求
            fetcher: (context, params) =&gt; fetch(
                limit: params['limit'] as int, page: params['page'] as int),
            // errorBuilder 将在发生错误时被调用,以可视化错误
            errorBuilder: (context, error) =&gt; ErrorWidget(error),
            // loadingIndicator 将在数据获取期间构建
            loadingIndicator: (context) =&gt;
                const Center(child: CircularProgressIndicator()),
            // viewBuilder 可视化从 fetcher 接收的 TModels 列表
            viewBuilder: (context, data) =&gt; CitiesView(data.data),
            // 您还可以为 fetch 开始结束事件提供监听器。它们将在 post-frame 回调中调用,因此您可以调用 setState
            onFetch: () {
              if (!mounted) return;
              setState(() =&gt; _areControlsLocked = true);
            },
            onLoaded: (res) {
              if (!mounted || res == null) return;
              setState(() {
                _metadata = res.meta;
                _areControlsLocked = false;
              });
            },
          ),

          // 分页控件
          if (meta == null)
            const SizedBox()
          else
            Row(
              children: [
                IconButton(
                  onPressed: !_areControlsLocked &amp;&amp; meta.currentPage &gt; 1
                      ? () =&gt; _controller.page--
                      : null,
                  icon: const Icon(Icons.keyboard_arrow_left),
                ),
                Text('${_controller.page}'),
                IconButton(
                  onPressed:
                      !_areControlsLocked &amp;&amp; meta.currentPage &lt; meta.totalPages
                          ? () =&gt; _controller.page++
                          : null,
                  icon: const Icon(Icons.keyboard_arrow_right),
                ),
              ],
            ),
        ],
      ),
    );
  }
}

更多关于Flutter分页请求插件flutter_nestjs_paginate的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter分页请求插件flutter_nestjs_paginate的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,flutter_nestjs_paginate 是一个用于 Flutter 与 NestJS 后端进行分页数据交互的插件。它通常用于从 NestJS 后端获取分页数据并在 Flutter 前端展示。以下是一个简单的示例,展示了如何使用这个插件。

后端(NestJS)

首先,确保你的 NestJS 项目已经设置好并包含必要的分页逻辑。以下是一个简单的控制器示例,它返回一个分页的用户列表。

// user.controller.ts
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { PaginateResult } from 'mongoose';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
import { PaginationDto } from './dto/pagination.dto';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @UseGuards(AuthGuard('jwt'))
  @Get()
  async findAll(
    @Query() paginationDto: PaginationDto,
  ): Promise<PaginateResult<User>> {
    return this.userService.findAll(paginationDto);
  }
}

前端(Flutter)

在你的 Flutter 项目中,你需要添加 flutter_nestjs_paginate 依赖项。在 pubspec.yaml 文件中添加以下依赖项:

dependencies:
  flutter:
    sdk: flutter
  flutter_nestjs_paginate: ^最新版本号

然后运行 flutter pub get

接下来,你可以使用 FlutterNestjsPaginate 类来进行分页请求。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_nestjs_paginate/flutter_nestjs_paginate.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  FlutterNestjsPaginate<UserModel> _paginate;
  List<UserModel> _users = [];

  @override
  void initState() {
    super.initState();
    _paginate = FlutterNestjsPaginate<UserModel>(
      client: http.Client(),
      baseUrl: 'https://your-nestjs-api-url/users',
      itemBuilder: (data) => UserModel.fromJson(data),
      pageParam: 'page',
      limitParam: 'limit',
    );

    // Fetch first page data
    _fetchData(page: 1, limit: 10);
  }

  Future<void> _fetchData({int page, int limit}) async {
    try {
      var response = await _paginate.fetchPage(page, limit);
      setState(() {
        _users = response.data;
      });
    } catch (e) {
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter NestJS Paginate Demo'),
      ),
      body: ListView.builder(
        itemCount: _users.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_users[index].name),
            subtitle: Text(_users[index].email),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Fetch next page data
          setState(() {
            int currentPage = (_paginate.currentPage ?? 1) + 1;
            _fetchData(page: currentPage, limit: 10);
          });
        },
        tooltip: 'Fetch Next Page',
        child: Icon(Icons.add),
      ),
    );
  }
}

class UserModel {
  String id;
  String name;
  String email;

  UserModel({this.id, this.name, this.email});

  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

解释

  1. 后端:在 NestJS 中,我们定义了一个 UserController,它接受分页参数并返回分页结果。
  2. 前端:在 Flutter 中,我们使用了 FlutterNestjsPaginate 类来与后端进行分页请求。
    • baseUrl 是你的 NestJS API 的基础 URL。
    • itemBuilder 用于将 JSON 数据转换为 UserModel 对象。
    • pageParamlimitParam 是分页参数名,与 NestJS 后端对应。
  3. 数据获取:通过 _fetchData 方法获取分页数据,并在 ListView 中展示。
  4. 加载更多:点击浮动按钮时,加载下一页的数据。

这个示例展示了如何使用 flutter_nestjs_paginate 插件在 Flutter 中进行分页请求,并与 NestJS 后端进行交互。希望这对你有所帮助!

回到顶部