pd_develop_kit 0.1.0 copy "pd_develop_kit: ^0.1.0" to clipboard
pd_develop_kit: ^0.1.0 copied to clipboard

跨平台 Flutter 开发工具包插件,提供网络抽象、校验工具、深度比较、扩展与 UI 能力。

example/lib/main.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:pd_develop_kit/pd_develop_kit.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const String _unknownPlatformVersion = 'Unknown platform version';

  String _platformVersion = _unknownPlatformVersion;
  final _pdDevelopKitPlugin = PdDevelopKit();
  late final PDApiClient _demoClient;
  late final _DemoLogger _logger;
  DemoResult? _lastResult;
  String _lastNetworkLog = '';

  @override
  void initState() {
    super.initState();
    _logger = _DemoLogger();
    _logger.lastLog.addListener(_onLogChanged);
    _configureDemoNetwork();
    _demoClient = PDNetwork.client;
    initPlatformState();
  }

  @override
  void dispose() {
    _logger.lastLog.removeListener(_onLogChanged);
    super.dispose();
  }

  void _onLogChanged() {
    if (!mounted) {
      return;
    }
    setState(() {
      _lastNetworkLog = _logger.lastLog.value;
    });
  }

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion =
          await _pdDevelopKitPlugin.getPlatformVersion() ??
          _unknownPlatformVersion;
    } catch (_) {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) {
      return;
    }

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  Future<void> _runScenario(_DemoScenario scenario) async {
    final BaseResponse<dynamic> response;

    switch (scenario) {
      case _DemoScenario.success:
        response = await _demoClient.get<Map<String, dynamic>>('/demo/success');
      case _DemoScenario.legacySuccess:
        response = await _demoClient.get<Map<String, dynamic>>(
          '/demo/legacy-success',
        );
      case _DemoScenario.businessError:
        response = await _demoClient.get<dynamic>('/demo/business-error');
      case _DemoScenario.serverError:
        response = await _demoClient.get<dynamic>('/demo/server-error');
      case _DemoScenario.timeout:
        response = await _demoClient.get<dynamic>('/demo/timeout');
      case _DemoScenario.mapperError:
        response = await _demoClient.get<int>(
          '/demo/mapper-error',
          mapper: (json) {
            throw const FormatException('demo mapper error');
          },
        );
      case _DemoScenario.perRequestOverride:
        response = await _demoClient.get<Map<String, dynamic>>(
          '/demo/success',
          requestConfig: const PDRequestConfig(
            baseUrl: 'https://override.example.com',
            headers: <String, dynamic>{'X-From': 'requestConfig'},
            enableLog: true,
          ),
        );
      case _DemoScenario.decryptDataField:
        response = await _demoClient.get<Map<String, dynamic>>(
          '/demo/encrypted-data-field',
          requestConfig: PDRequestConfig(
            responseDecoder: PDAesResponseDecoder(
              decrypt: (cipher) => cipher.replaceFirst('ENC:', ''),
              mode: PDDecryptMode.dataFieldOnly,
            ),
          ),
        );
      case _DemoScenario.encryptRequestBody:
        response = await _demoClient.post<Map<String, dynamic>>(
          '/demo/encrypted-request-body',
          data: <String, dynamic>{'a': 1, 'b': true},
          requestConfig: PDRequestConfig(
            requestEncoder: PDAesRequestEncoder(
              encrypt: (plain) => 'ENC:$plain',
              bodyCipherField: 'cipherText',
            ),
          ),
        );
    }

    if (!mounted) {
      return;
    }

    setState(() {
      _lastResult = DemoResult(
        title: scenario.title,
        description: scenario.description,
        response: response,
      );
    });
  }

  void _configureDemoNetwork() {
    PDNetwork.configure(
      transport: _DemoTransport(
        onSend: ({required request, cancelToken}) async {
          await Future<void>.delayed(const Duration(milliseconds: 250));

          switch (request.path) {
            case '/demo/success':
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '查询成功',
                  'data': <String, dynamic>{
                    'userName': 'Pedro',
                    'role': 'maintainer',
                    'from': 'standard-envelope',
                    'baseUrl': request.config.baseUrl,
                  },
                },
              );
            case '/demo/legacy-success':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'msg': '旧接口也能正常解析',
                  'data': <String, dynamic>{
                    'enabled': true,
                    'from': 'legacy-envelope',
                  },
                },
              );
            case '/demo/business-error':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 403,
                  'success': false,
                  'msg': '没有访问权限',
                  'data': null,
                },
              );
            case '/demo/server-error':
              return const PDTransportResponse(
                statusCode: 500,
                data: <String, dynamic>{'msg': '服务器异常,请稍后重试'},
              );
            case '/demo/timeout':
              throw const PDTransportException(
                type: PDTransportErrorType.connectionTimeout,
                message: 'Connection timeout',
              );
            case '/demo/mapper-error':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '映射前响应正常',
                  'data': <String, dynamic>{'value': 'unexpected'},
                },
              );
            case '/demo/encrypted-data-field':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': 'data 字段已加密(模拟)',
                  'data':
                      'ENC:{"value":123,"note":"decoded by responseDecoder"}',
                },
              );
            case '/demo/encrypted-request-body':
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '请求体已加密(模拟)',
                  'data': <String, dynamic>{'receivedBody': request.body},
                },
              );
            default:
              return const PDTransportResponse(
                statusCode: 404,
                data: <String, dynamic>{'msg': '未找到示例路由'},
              );
          }
        },
      ),
      config: PDNetworkConfig(
        baseUrl: 'https://demo.pd-develop-kit.local',
        enableLog: true,
        defaultErrorMessage: '请求失败,请稍后重试。',
        logger: _logger,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('PD Develop Kit Example')),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              _SectionCard(
                title: '平台能力',
                child: Text(
                  'Running on: $_platformVersion',
                  key: const Key('platform-version-text'),
                ),
              ),
              const SizedBox(height: 16),
              const _SectionCard(
                title: '网络层示例说明',
                child: SelectableText(
                  '这个页面使用 PDNetwork.configure + PDApiClient + 模拟 PDTransport 来演示网络层用法。\n'
                  '重点验证:无论成功、业务失败、服务器错误还是异常,调用方最终拿到的都是 BaseResponse。',
                ),
              ),
              const SizedBox(height: 16),
              _SectionCard(
                title: '初始化示例',
                child: SelectableText(
                  [
                    "PDNetwork.configure(\n",
                    "  config: const PDNetworkConfig(\n",
                    "    baseUrl: 'https://api.example.com',\n",
                    "    headers: <String, dynamic>{'Accept': 'application/json'},\n",
                    "    enableLog: true,\n",
                    "  ),\n",
                    ");\n\n",
                    "final client = PDNetwork.client;\n",
                    "final BaseResponse<Map<String, dynamic>> response =\n",
                    "    await client.get<Map<String, dynamic>>('/user/info');\n\n",
                    "final overrideResp = await client.get<Map<String, dynamic>>(\n",
                    "  '/user/info',\n",
                    "  requestConfig: const PDRequestConfig(\n",
                    "    headers: <String, dynamic>{'X-Debug': '1'},\n",
                    "    enableLog: false,\n",
                    "  ),\n",
                    ");",
                  ].join(),
                  key: const Key('network-code-snippet'),
                ),
              ),
              const SizedBox(height: 16),
              _SectionCard(
                title: '场景演示',
                child: Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: _DemoScenario.values.map((scenario) {
                    return FilledButton.tonal(
                      key: Key('scenario-${scenario.name}'),
                      onPressed: () => _runScenario(scenario),
                      child: Text(scenario.title),
                    );
                  }).toList(),
                ),
              ),
              const SizedBox(height: 16),
              _SectionCard(
                title: '最近一次响应',
                child: _ResultView(result: _lastResult),
              ),
              const SizedBox(height: 16),
              _SectionCard(
                title: '最近一次网络日志(示例 logger)',
                child: SelectableText(
                  _lastNetworkLog.isEmpty ? '(empty)' : _lastNetworkLog,
                  key: const Key('network-last-log'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

enum _DemoScenario {
  success(title: '标准成功', description: '标准 {code, success, msg, data} 响应'),
  legacySuccess(title: '旧接口成功', description: '只有 code=200,也会按成功解析'),
  businessError(title: '业务失败', description: 'HTTP 200 但业务 code 非 200'),
  serverError(title: '服务器错误', description: 'HTTP 500 会转成 BaseResponse.error'),
  timeout(title: '网络超时', description: 'Transport 异常不会向外抛出'),
  mapperError(title: '映射异常', description: 'mapper 抛错也会收敛为 BaseResponse.error'),
  perRequestOverride(
    title: '单次配置覆盖',
    description: 'PDRequestConfig 仅影响当前请求,不污染全局配置',
  ),
  decryptDataField(
    title: '解密 data 字段',
    description: 'responseDecoder 解密 data 字段(模拟)',
  ),
  encryptRequestBody(title: '加密请求体', description: 'requestEncoder 加密请求体(模拟)');

  const _DemoScenario({required this.title, required this.description});

  final String title;
  final String description;
}

class DemoResult {
  const DemoResult({
    required this.title,
    required this.description,
    required this.response,
  });

  final String title;
  final String description;
  final BaseResponse<dynamic> response;
}

class _SectionCard extends StatelessWidget {
  const _SectionCard({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            child,
          ],
        ),
      ),
    );
  }
}

class _ResultView extends StatelessWidget {
  const _ResultView({required this.result});

  final DemoResult? result;

  @override
  Widget build(BuildContext context) {
    if (result == null) {
      return const Text(
        '点击上方任意场景按钮后,这里会展示 BaseResponse 的结果内容。',
        key: Key('result-placeholder'),
      );
    }

    final BaseResponse<dynamic> response = result!.response;
    final String prettyData = _prettyJson(response.data);
    final String prettyRawData = _prettyJson(response.rawData);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Text(
          '场景: ${result!.title}',
          key: const Key('result-title'),
          style: Theme.of(context).textTheme.titleSmall,
        ),
        const SizedBox(height: 8),
        Text(result!.description, key: const Key('result-description')),
        const SizedBox(height: 12),
        Text(
          'isSuccess: ${response.isSuccess}',
          key: const Key('result-success'),
        ),
        Text('code: ${response.code}', key: const Key('result-code')),
        Text(
          'httpStatusCode: ${response.httpStatusCode ?? '-'}',
          key: const Key('result-http-code'),
        ),
        Text(
          'msg: ${response.msg.isEmpty ? '(empty)' : response.msg}',
          key: const Key('result-message'),
        ),
        const SizedBox(height: 12),
        SelectableText('data:\n$prettyData', key: const Key('result-data')),
        const SizedBox(height: 12),
        SelectableText(
          'rawData:\n$prettyRawData',
          key: const Key('result-raw-data'),
        ),
      ],
    );
  }

  String _prettyJson(dynamic value) {
    if (value == null) {
      return 'null';
    }

    try {
      return const JsonEncoder.withIndent('  ').convert(value);
    } catch (_) {
      return value.toString();
    }
  }
}

class _DemoTransport extends PDTransport {
  const _DemoTransport({required this.onSend});

  final Future<PDTransportResponse> Function({
    required PDRequest request,
    PDCancelToken? cancelToken,
  })
  onSend;

  @override
  Future<PDTransportResponse> send({
    required PDRequest request,
    PDCancelToken? cancelToken,
  }) {
    return onSend(request: request, cancelToken: cancelToken);
  }
}

class _DemoLogger extends PDNetworkLogger {
  _DemoLogger();

  final ValueNotifier<String> lastLog = ValueNotifier<String>('');

  @override
  void onRequest(PDRequest request) {
    lastLog.value = [
      'REQUEST',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'headers=${request.headers}',
      'query=${request.queryParameters}',
      'body=${request.body}',
    ].join('\n');
  }

  @override
  void onResponse({
    required PDRequest request,
    required PDTransportResponse response,
  }) {
    lastLog.value = [
      'RESPONSE',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'status=${response.statusCode}',
      'data=${response.data}',
    ].join('\n');
  }

  @override
  void onError({
    required PDRequest request,
    required PDTransportException exception,
  }) {
    lastLog.value = [
      'ERROR',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'type=${exception.type}',
      'status=${exception.statusCode ?? '-'}',
      'message=${exception.message}',
    ].join('\n');
  }
}
1
likes
0
points
18
downloads

Publisher

unverified uploader

Weekly Downloads

跨平台 Flutter 开发工具包插件,提供网络抽象、校验工具、深度比较、扩展与 UI 能力。

Repository

Topics

#flutter #plugin #network #validator

License

unknown (license)

Dependencies

dio, flutter, flutter_web_plugins, global_event_bus, pd_cooldown, pd_load_state, pd_log, plugin_platform_interface, random_toolkit, web

More

Packages that depend on pd_develop_kit

Packages that implement pd_develop_kit