Flutter数据响应式管理插件rxdata的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter数据响应式管理插件rxdata的使用

RxData 是一个用于Flutter的数据响应式管理插件,它允许你将数据获取和缓存行为委托给它处理,并且在后台使用了 flutter_bloc。该插件受到了 Revolut’s RxData 库的启发。

安装

你可以通过以下命令安装 rxdata

flutter pub add rxdata

使用

1. 定义 DataDelegate

首先,你需要定义一个 DataDelegate 对象,并指定其数据类型。例如,我们以 ApiResponse 类型为例:

final dataDelegate = DataDelegate<ApiResponse>(
  fromNetwork: () async* {
    // 可以多次调用 yield 来逐步返回数据,但要防止无限流
    final response = await getRequest();
    yield response;
  },
  fromStorage: () async {
    return loadFromSqlite('my_key');
  },
  toStorage: (value) async {
    await saveToSqlite(value, 'my_key');
  },
);

2. 使用 DataBuilder 构建UI

接下来,你可以使用 DataBuilder 来构建你的UI界面。DataBuilder 实际上是 BlocBuilder 的包装器,你也可以使用 DataListenerDataConsumer

class ExampleWidget extends StatelessWidget {
  const ExampleWidget({Key? key, required this.dataDelegate}) : super(key: key);

  final DataDelegate<ApiResponse> dataDelegate;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DataBuilder<ApiResponse>(
        bloc: dataDelegate,
        builder: (context, state) {
          return Column(
            children: [
              if (state.isLoading) const CircularProgressIndicator(),
              if (state.hasError) Text(state.error!.toString()),
              if (state.hasValue) Text(state.value!.toString()),
            ],
          );
        },
      ),
    );
  }
}

3. Data 类的字段

Data 类包含以下三个字段:

  • value: 如 ApiResponse 或其他你需要的数据类型。
  • error: 可选错误,即使发生错误,value 也不会被删除。
  • isLoading: 如果预期 valueerror 即将发生变化,则为 true

你可以通过调用 dataDelegate.reload() 来重新获取数据。只要指定了回调函数,缓存就会自动处理。

示例项目

以下是一个完整的示例项目,展示了如何使用 rxdata 插件来获取比特币价格历史数据,并在UI中显示。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:rxdata/rxdata.dart';

final priceFormat = NumberFormat.currency(name: 'USD', symbol: r'$');

Future<void> main() async {
  await Hive.initFlutter();
  runApp(ExampleApp());
}

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

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late final DataDelegate<ApiResponse> dataDelegate;

  bool wasErrorThrown = false;

  @override
  void initState() {
    super.initState();
    dataDelegate = DataDelegate(
      fromNetwork: () async* {
        final now = DateTime.now();

        final uri = Uri(
          host: 'api.coincap.io',
          path: 'v2/assets/bitcoin/history',
          scheme: 'https',
          queryParameters: <String, dynamic>{
            'interval': 'm1',
            'start': now.subtract(const Duration(minutes: 10)).millisecondsSinceEpoch.toString(),
            'end': now.millisecondsSinceEpoch.toString(),
          },
        );

        if (!wasErrorThrown) {
          wasErrorThrown = true;
          throw Exception('Example error. Try pressing reload icon.');
        }

        await Future<void>.delayed(const Duration(seconds: 1));

        final response = await http.get(uri);

        if (response.statusCode != 200) {
          throw Exception(response.body);
        }

        yield ApiResponse.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
      },
      fromStorage: () async {
        print('[fromStorage]');
        final box = await Hive.openBox<String>('storage');
        final data = box.get('data');
        if (data == null) {
          return null;
        }
        final json = jsonDecode(data) as Map<String, dynamic>;
        return ApiResponse.fromJson(json);
      },
      toStorage: (value) async {
        print('[toStorage]');
        final box = await Hive.openBox<String>('storage');
        await box.put('data', value.toJsonString());
      },
      onClearCache: () async {
        await Hive.deleteBoxFromDisk('storage');
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: DataBuilder<ApiResponse>(
        bloc: dataDelegate,
        builder: (context, state) {
          return CustomScrollView(
            slivers: [
              SliverAppBar(
                title: const Text('Example'),
                pinned: true,
                actions: [
                  InkWell(
                    onTap: dataDelegate.reload,
                    child: const Padding(
                      padding: EdgeInsets.all(16.0),
                      child: Icon(Icons.refresh),
                    ),
                  ),
                ],
              ),
              CupertinoSliverRefreshControl(
                onRefresh: dataDelegate.reload,
              ),
              SliverToBoxAdapter(
                child: Text('Last updated: ${dataDelegate.lastUpdated}'),
              ),
              const SliverToBoxAdapter(
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Card(
                    child: Padding(
                      padding: EdgeInsets.all(24.0),
                      child: Text(
                        'Fetch BTC price history in the past 10 minutes.',
                      ),
                    ),
                  ),
                ),
              ),
              if (state.isLoading)
                SliverToBoxAdapter(
                  child: Row(
                    children: const [
                      CircularProgressIndicator(),
                      Text('Loading...')
                    ],
                  ),
                ),
              if (state.hasError)
                SliverToBoxAdapter(
                  child: Container(
                    color: Colors.orangeAccent,
                    padding: const EdgeInsets.all(16),
                    margin: const EdgeInsets.all(10),
                    child: Text(state.error.toString()),
                  ),
                ),
              if (state.hasValue)
                SliverList(
                  delegate: SliverChildListDelegate(
                    state.value!.data.map((e) {
                      return ListTile(
                        title: Text(
                          priceFormat.format(num.parse(e.priceUsd)),
                        ),
                        subtitle: Text(e.date.toLocal().toIso8601String()),
                      );
                    }).toList(),
                  ),
                )
              else
                const SliverToBoxAdapter(
                  child: Text('No data'),
                ),
            ],
          );
        },
      ),
    );
  }
}

class ApiResponse {
  const ApiResponse({required this.data});

  factory ApiResponse.fromJson(Map<String, dynamic> json) {
    final values = json['data'] as List<dynamic>;
    return ApiResponse(
      data: values
          .map((dynamic e) => PricePoint.fromJson(e as Map<String, dynamic>))
          .sortedBy((element) => element.date)
          .reversed
          .toList(),
    );
  }

  final List<PricePoint> data;

  String toJsonString() {
    return jsonEncode(toJson());
  }

  Map<String, Object?> toJson() {
    return {
      'data': data.map((e) => e.toJson()).toList(),
    };
  }
}

class PricePoint {
  const PricePoint({
    required this.priceUsd,
    required this.date,
  });

  factory PricePoint.fromJson(Map<String, Object?> json) {
    return PricePoint(
      priceUsd: json['priceUsd'] as String,
      date: DateTime.parse(json['date'] as String),
    );
  }

  final String priceUsd;
  final DateTime date;

  Map<String, Object?> toJson() {
    return {
      'priceUsd': priceUsd,
      'date': date.toIso8601String(),
    };
  }
}

日志记录

如果你想捕获 DataDelegate 中的事件和错误,可以使用 BLoC 的 BlocObserver。更多详情请参考 BLoC 文档

一个简单的 BlocObserver 实现如下:

class SimpleBlocObserver extends BlocObserver {
  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
    super.onError(bloc, error, stackTrace);
    print('Error: $error');
  }

  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    if (bloc is DataDelegate<ApiResponse>) {
      final state = change.nextState as Data<ApiResponse>;
      print('[SimpleBlocObserver.onChange] Next state $state');
    }
  }
}

以上就是关于 rxdata 插件的基本使用方法及完整示例代码。希望对你有所帮助!


更多关于Flutter数据响应式管理插件rxdata的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据响应式管理插件rxdata的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用rxdart库(通常与响应式编程中的RxData概念相关,尽管Flutter社区中没有一个特定的rxdata插件,但rxdart是实现响应式编程的常用库)的简单示例。这个示例将展示如何使用rxdartBehaviorSubject来管理数据响应式更新。

首先,确保你已经在pubspec.yaml文件中添加了rxdart依赖:

dependencies:
  flutter:
    sdk: flutter
  rxdart: ^0.27.3  # 请检查最新版本号

然后,运行flutter pub get来获取依赖。

接下来,我们创建一个简单的Flutter应用,它使用rxdartBehaviorSubject来管理一个字符串数据的响应式更新。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

class _MyHomePageState extends State<MyHomePage> {
  // 创建一个BehaviorSubject来管理字符串数据
  final BehaviorSubject<String> _subject = BehaviorSubject<String>();

  // 获取数据流的监听器
  Stream<String> get dataStream => _subject.stream;

  @override
  void dispose() {
    // 释放资源
    _subject.close();
    super.dispose();
  }

  void updateData(String newData) {
    // 更新数据
    _subject.add(newData);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('RxDart Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Current Data:',
              style: TextStyle(fontSize: 20),
            ),
            StreamBuilder<String>(
              stream: dataStream,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text(
                    snapshot.data!,
                    style: TextStyle(fontSize: 24, color: Colors.blue),
                  );
                } else {
                  return Text('No Data');
                }
              },
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                updateData('Hello, Flutter with RxDart!');
              },
              child: Text('Update Data'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个BehaviorSubject<String>来管理字符串数据。BehaviorSubject会缓存最新的值,并且新的订阅者会立即接收到这个缓存的值。
  2. 我们定义了一个dataStream getter,它返回_subject的流。
  3. dispose方法中,我们关闭了_subject以释放资源。
  4. updateData方法用于向_subject添加新的数据值。
  5. 在UI部分,我们使用StreamBuilder来监听dataStream的变化,并根据最新的数据更新UI。

这个示例展示了如何使用rxdart库来实现Flutter中的数据响应式管理。你可以根据需要扩展这个示例,以适应更复杂的数据管理场景。

回到顶部