Flutter离线优先FHIR客户端插件offline_first_fhir_client的使用
Flutter离线优先FHIR客户端插件offline_first_fhir_client的使用
介绍
“offline_first_fhir_client” 是一个用于在 Flutter 应用中实现离线优先数据同步功能的插件。该插件通过结合本地数据库(Hive)和远程服务器(如 HAPI FHIR),实现了数据的高效存储、管理和同步。
本文将详细介绍如何使用此插件,并提供完整的示例代码。
插件结构与依赖
包文件夹结构
(my_flutter_package)/
├── lib/
│ ├── src/
│ │ ├── api/
│ │ ├── syncing/
│ │ ├── database/
│ │ ├── conflict_resolution/
│ │ ├── encryption/
│ │ └── offline_first_fhir_client.dart
│ ├── offline_first_fhir_client.dart
├── example/
│ ├── lib/
│ │ └── main.dart
│ └── pubspec.yaml
├── test/
│ ├── api/
│ ├── database/
│ └── offline_first_fhir_client.dart
├── .gitignore
├── analysis_options.yaml
├── CHANGELOG.md
├── LICENSE
├── pubspec.lock
├── pubspec.yaml
└── README.md
关键依赖类
以下为插件的核心依赖类及其功能:
classDiagram
direction LR
class EncryptionHelper {
+storeEncryptionKey()
+retrieveEncryptionKey()
+generateSecureKey()
}
class DatabaseController {
+initializeHiveDatabase()
+createData()
+readData()
+updateData()
+deleteData()
+resolveConflicts()
}
class SyncScheduler {
-syncInterval: Duration
+scheduleSyncOperation()
+runPeriodicSync()
+triggerManualSync()
}
class InternetChecker {
+checkConnectivity()
+monitorInternetStatus()
+isInternetAvailable(): Boolean
}
class ApiService {
+authorization()
+postRequest()
+putRequest()
+deleteRequest()
+getRequest()
+updateRequest()
+resolveApiConflicts()
}
EncryptionHelper --> DatabaseController: secure data storage
DatabaseController --> SyncScheduler: data sync management
SyncScheduler --> InternetChecker: connectivity verification
InternetChecker --> ApiService: network status
功能概述
1. Hive 数据库
Hive 是一个轻量级的 NoSQL 数据库,用于本地存储数据。它支持加密存储,确保数据安全。
2. API 调用
ApiService
类负责所有 API 操作,包括授权、POST、PUT、DELETE 等请求。
3. 同步调度
SyncScheduler
类管理数据同步任务,支持手动和定时同步(例如每两天一次)。
4. 网络检测
InternetChecker
使用 connectivity_plus
库检查网络状态,确保同步操作在网络可用时执行。
示例代码
以下是一个完整的 Flutter 应用示例,展示如何使用 offline_first_fhir_client
插件进行离线优先数据同步。
示例代码:example/lib/main.dart
import 'package:example/edit_patient_data.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:offline_first_fhir_client/offline_first_fhir_client.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化 FhirClient
await FhirClient().initialize("https://fhir-server-persisted.wonderfulcliff-68cdbf49.westeurope.azurecontainerapps.io/hapi-fhir-jpaserver/fhir", "");
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Patient Data Manager',
home: PatientInputScreen(),
);
}
}
class PatientInputScreen extends StatefulWidget {
const PatientInputScreen({super.key});
@override
PatientInputScreenState createState() => PatientInputScreenState();
}
class PatientInputScreenState extends State<PatientInputScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _birthDateController = TextEditingController();
final fhirClient = FhirClient();
final List<Map<String, dynamic>> _patients = [];
bool isLoading = false;
@override
void dispose() {
_nameController.dispose();
_birthDateController.dispose();
super.dispose();
}
@override
void initState() {
_getPatients();
super.initState();
}
/// 获取所有患者数据并更新状态
void _getPatients() async {
_patients.clear();
List<Map<String, dynamic>>? patientsData = await fhirClient.getAllResources("Patient");
setState(() {
_patients.addAll(patientsData);
});
}
/// 添加新患者或更新现有患者
Future<void> _addPatient() async {
if (_formKey.currentState!.validate()) {
final patient = {
"id": DateTime.now().millisecondsSinceEpoch.toString(),
"localVersionId": "1",
"isSynced": false,
"body": {
"resourceType": "Patient",
"name": [{"family": _nameController.text.split(" ").last, "given": [_nameController.text.split(" ").first]}],
"birthDate": _birthDateController.text,
"meta": {
"versionId": "1",
"lastUpdated": DateFormat("yyyy-MM-ddTHH:mm:ss.SSSXXX").format(DateTime.now().toUtc()),
},
},
};
setState(() {
_patients.add(patient);
});
await _savePatientToLocal(patient);
_clearFormInputs();
}
}
/// 清空表单输入
void _clearFormInputs() {
_nameController.clear();
_birthDateController.clear();
}
/// 将患者数据保存到本地数据库
Future<void> _savePatientToLocal(Map<String, dynamic> patient) async {
await fhirClient.saveResource("Patient", patient['id'], patient);
}
/// 同步所有患者数据到服务器
Future<void> _syncPatientsToServer() async {
setState(() {
isLoading = true;
});
try {
await fhirClient.syncResources(["Patient"], "/Patient/\$everything", {});
_getPatients();
} catch (e) {
if (kDebugMode) {
print("Sync error: $e");
}
} finally {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Patient Data Input')),
body: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
return null;
},
),
TextFormField(
controller: _birthDateController,
decoration: const InputDecoration(labelText: 'Birth Date (YYYY-MM-DD)'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a birth date';
}
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _addPatient,
child: const Text('Add Patient to Local'),
),
],
),
),
const SizedBox(height: 20),
const Text('Patients From Local Database'),
Expanded(
child: ListView.builder(
itemCount: _patients.length,
itemBuilder: (context, index) {
final patient = _patients[index];
return ListTile(
title: Text("${patient['body']['name'][0]['given'][0]} ${patient['body']['name'][0]['family']}"),
subtitle: Text('${patient['body']['birthDate']}'),
trailing: !patient['isSynced']
? IconButton(
onPressed: () async {
await fhirClient.deleteResourceByKey("Patient", patient['id']);
setState(() {
_patients.removeAt(index);
});
},
icon: const Icon(Icons.delete, color: Colors.red),
)
: null,
);
},
),
),
const Text('New data will reflect on server within 5 mins'),
ElevatedButton(
onPressed: isLoading ? null : _syncPatientsToServer,
child: Text(isLoading ? 'Syncing' : 'Sync All to Server'),
),
],
),
),
isLoading ? const Center(child: CircularProgressIndicator()) : Container(),
],
),
);
}
}
运行步骤
-
初始化项目
- 打开 Android Studio。
- 选择
File > Open
,导航到项目根目录并打开。
-
配置运行环境
- 修改
baseUrl
和authToken
:final apiService = ApiService(baseUrl: "http://192.168.1.8:8080/fhir", authToken: "your-auth-token");
- 修改
更多关于Flutter离线优先FHIR客户端插件offline_first_fhir_client的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter离线优先FHIR客户端插件offline_first_fhir_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
offline_first_fhir_client
是一个 Flutter 插件,旨在帮助开发者构建支持离线优先的 FHIR 客户端应用。FHIR(Fast Healthcare Interoperability Resources)是一种用于医疗数据交换的标准,而离线优先意味着应用在没有网络连接的情况下仍然可以正常工作,并在网络恢复时同步数据。
主要功能
- 离线存储:在本地存储 FHIR 资源,以便在没有网络连接时访问。
- 数据同步:在网络恢复时,自动将本地数据与远程服务器同步。
- 缓存管理:管理缓存数据,确保数据的一致性和时效性。
- 错误处理:处理网络错误和同步冲突,确保数据的完整性。
安装
在 pubspec.yaml
文件中添加依赖:
dependencies:
offline_first_fhir_client: ^0.1.0
然后运行 flutter pub get
来安装插件。
基本用法
-
初始化客户端: 首先,你需要初始化
OfflineFirstFhirClient
,并指定 FHIR 服务器的 URL。import 'package:offline_first_fhir_client/offline_first_fhir_client.dart'; final client = OfflineFirstFhirClient( baseUrl: 'https://your-fhir-server.com', );
-
获取资源: 使用
getResource
方法从服务器或本地缓存中获取 FHIR 资源。final patient = await client.getResource('Patient', '123'); print(patient);
-
创建或更新资源: 使用
createResource
或updateResource
方法来创建或更新 FHIR 资源。这些操作会先在本地执行,然后在网络恢复时同步到服务器。final newPatient = Patient( id: '123', name: [HumanName(text: 'John Doe')], ); await client.createResource(newPatient);
-
同步数据: 手动触发数据同步,以确保本地数据与服务器保持一致。
await client.sync();
-
处理同步冲突: 在同步过程中可能会遇到冲突,你可以通过实现
ConflictResolver
来处理这些冲突。client.conflictResolver = MyConflictResolver();
示例代码
以下是一个简单的示例,展示了如何使用 offline_first_fhir_client
插件来获取和创建 FHIR 资源。
import 'package:flutter/material.dart';
import 'package:offline_first_fhir_client/offline_first_fhir_client.dart';
import 'package:fhir/r4.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
[@override](/user/override)
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final client = OfflineFirstFhirClient(
baseUrl: 'https://your-fhir-server.com',
);
Patient? patient;
[@override](/user/override)
void initState() {
super.initState();
fetchPatient();
}
Future<void> fetchPatient() async {
final fetchedPatient = await client.getResource('Patient', '123');
setState(() {
patient = fetchedPatient as Patient?;
});
}
Future<void> createPatient() async {
final newPatient = Patient(
id: '124',
name: [HumanName(text: 'Jane Doe')],
);
await client.createResource(newPatient);
await client.sync();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Offline First FHIR Client'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (patient != null)
Text('Patient: ${patient!.name?.first.text}'),
ElevatedButton(
onPressed: createPatient,
child: Text('Create Patient'),
),
],
),
),
);
}
}