Flutter数据响应式管理插件rxdata的使用
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
的包装器,你也可以使用 DataListener
和 DataConsumer
。
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
: 如果预期value
或error
即将发生变化,则为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
更多关于Flutter数据响应式管理插件rxdata的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用rxdart
库(通常与响应式编程中的RxData
概念相关,尽管Flutter社区中没有一个特定的rxdata
插件,但rxdart
是实现响应式编程的常用库)的简单示例。这个示例将展示如何使用rxdart
的BehaviorSubject
来管理数据响应式更新。
首先,确保你已经在pubspec.yaml
文件中添加了rxdart
依赖:
dependencies:
flutter:
sdk: flutter
rxdart: ^0.27.3 # 请检查最新版本号
然后,运行flutter pub get
来获取依赖。
接下来,我们创建一个简单的Flutter应用,它使用rxdart
的BehaviorSubject
来管理一个字符串数据的响应式更新。
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'),
),
],
),
),
);
}
}
在这个示例中:
- 我们创建了一个
BehaviorSubject<String>
来管理字符串数据。BehaviorSubject
会缓存最新的值,并且新的订阅者会立即接收到这个缓存的值。 - 我们定义了一个
dataStream
getter,它返回_subject
的流。 - 在
dispose
方法中,我们关闭了_subject
以释放资源。 updateData
方法用于向_subject
添加新的数据值。- 在UI部分,我们使用
StreamBuilder
来监听dataStream
的变化,并根据最新的数据更新UI。
这个示例展示了如何使用rxdart
库来实现Flutter中的数据响应式管理。你可以根据需要扩展这个示例,以适应更复杂的数据管理场景。