Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card
Flutter ile Sağlık Uygulaması Nasıl Oluşturulur
Öğretici: OAuth onayı ve PKCE ile bir kişinin FHIR R4 kaydına bağlanan, belirteçleri güvenli bir şekilde saklayan, kan basıncı okumalarını Dart SDK ile okuyup ekleyen ve erişim iptalini yöneten bir Flutter uygulaması.
Bu eğitim, bir kişinin HL7 FHIR R4 tıbbi kaydına onaylarıyla bağlanan, kan basıncı okumalarını gösteren ve yenilerini ekleyen bir Flutter uygulaması oluşturur; Dart SDK anpheros_sdk kullanılır. Uygulama, kişinin verdiği bir OAuth token'ı ile doğrudan Anpheros ile konuşur, API anahtarı ile değil, bu nedenle tıbbi veriler için kendi arka ucuna ihtiyaç duymaz. iOS ve Android'de çalışır; aynı kod, auth paketinin platform kurulumuyla macOS ve web'de de çalışır.
Parçalar Nasıl Bir Araya Geliyor
┌──────────────────────┐ 1. "Connect" opens the consent screen ┌───────────────────────────┐
│ Flutter app │ ─────────────────────────────────────► │ Anpheros │
│ anpheros_sdk │ ◄───────────────────────────────────── │ sign-in + consent screen │
│ flutter_web_auth_2 │ 2. bpapp://callback?code=… │ │
│ flutter_secure_storage│ 3. code + PKCE verifier → tokens │ OAuth 2.1 / SMART on FHIR │
│ │ 4. read / write within the scopes │ FHIR R4 record, audit │
└──────────────────────┘ ─────────────────────────────────────► └───────────────────────────┘
Bir mobil uygulama asla bir API anahtarı içermemelidir: Herkes onu çıkarabilir ve bir anahtar projenizin her hastasına ulaşır. OAuth ile uygulamadaki jeton, kişinin onayladığı yalnızca bir kayda ulaşır ve bu, kişi iptal edene kadar geçerlidir. Akış, SMART on FHIR ile hasta onayının nasıl uygulanacağı bölümünde açıklanan standart SMART on FHIR bağımsız başlatmadır; SDK protokol işini yapar.
Başlamadan Önce
- Flutter 3.22 veya daha yenisi.
- Bir Anpheros projesi. platform.anpheros.com/dashboard adresinden Google ile giriş yapın ve bir sandbox anahtarı alın; proje bununla birlikte gelir.
- Dashboard'da, yönlendirme URI'si
bpapp://callbackolan public türünde bir uygulama kaydedin.client_id'unu not edin.
Özel beta sırasında, OAuth bağlantıları sandbox kayıtlarını kullanır, böylece tüm akışı gerçek veriler olmadan test edebilirsiniz.
flutter create bp_app && cd bp_app
flutter pub add anpheros_sdk flutter_web_auth_2 flutter_secure_storage
Android: android/app/src/main/AndroidManifest.xml içindeki <application>'de, şemanızla birlikte flutter_web_auth_2 geri çağırma etkinliğini ekleyin:
<activity android:name="com.linusu.flutter_web_auth_2.CallbackActivity" android:exported="true" android:taskAffinity="">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bpapp" />
</intent-filter>
</activity>
iOS, özel bir şema için ek kurulum gerektirmez.
Adım 1: kayda bağlanma
OAuthFlow, bir PKCE meydan okumasıyla yetkilendirme adresini oluşturur; flutter_web_auth_2 bunu güvenli bir tarayıcı sekmesinde açar ve Anpheros bpapp://callback'e yönlendirdiğinde geri döner.
// lib/record_connection.dart
import 'dart:convert';
import 'dart:math';
import 'package:anpheros_sdk/anpheros_sdk.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
const _base = 'https://platform.anpheros.com';
const _clientId = 'client_…'; // from the dashboard
const _scopes = [
'patient/Observation.rs?category=vital-signs', // read vital signs only
'patient/Observation.c', // add readings
'offline_access', // stay connected without asking again every 30 minutes
];
class RecordConnection {
final _flow = OAuthFlow(baseUrl: _base, clientId: _clientId, redirectUri: 'bpapp://callback');
final _storage = const FlutterSecureStorage();
/// Opens the consent screen and returns the stored tokens, or throws if the person declines.
Future<OAuthTokens> connect() async {
final pkce = Pkce.generate();
final state = base64UrlEncode(List<int>.generate(16, (_) => Random.secure().nextInt(256)));
final url = _flow.authorizeUrl(scopes: _scopes, state: state, pkce: pkce, purpose: 'self-care', lang: 'en');
final result = Uri.parse(await FlutterWebAuth2.authenticate(url: url.toString(), callbackUrlScheme: 'bpapp'));
if (result.queryParameters['state'] != state) throw StateError('state mismatch');
final code = result.queryParameters['code'];
if (code == null) throw StateError(result.queryParameters['error'] ?? 'no code');
final tokens = await _flow.exchange(code: code, pkce: pkce);
await _save(tokens);
return tokens;
}
/// The client for the connected record, refreshing and re-saving tokens as needed.
Future<(Anpheros, String)?> open() async {
final saved = await _storage.read(key: 'anpheros_tokens');
if (saved == null) return null;
final tokens = OAuthTokens.fromJson(jsonDecode(saved) as Map<String, dynamic>);
final client = Anpheros(
baseUrl: _base,
auth: AnpherosAuth.oauth(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresAt: tokens.expiresAt,
onRefresh: _flow.refresh,
onTokens: _save,
),
);
return (client, tokens.patient!);
}
Future<void> disconnect() async {
final saved = await _storage.read(key: 'anpheros_tokens');
if (saved != null) {
final tokens = OAuthTokens.fromJson(jsonDecode(saved) as Map<String, dynamic>);
await _flow.revoke(tokens.refreshToken ?? tokens.accessToken);
}
await _storage.delete(key: 'anpheros_tokens');
}
Future<void> _save(OAuthTokens tokens) =>
_storage.write(key: 'anpheros_tokens', value: jsonEncode(tokens.toJson()));
}
Onay ekranında kişi oturum açar, uygulamanızın adını, amacını ve tam olarak ne istediğini görür, kaydı (kendi kaydı, ilgilendiği bir kayıt veya uygulamanız için yeni bir kayıt) ve erişimin ne kadar süreceğini seçer. tokens.patient, uygulamanız için bu kaydın kimliğidir.
Yenileme belirteçleri döner: her yenileme yeni bir tane döndürür ve onTokens bunu hemen depolar. Eski bir yenileme belirtecini yeniden kullanmak izni geçersiz kılar, bu nedenle onTokens'i atlamayın.
Adım 2: Okumaları Oku
// lib/readings.dart
import 'package:anpheros_sdk/anpheros_sdk.dart';
class Reading {
Reading(this.measuredAt, this.systolic, this.diastolic);
final DateTime measuredAt;
final double systolic;
final double diastolic;
}
const _bloodPressure = '85354-9', _systolic = '8480-6', _diastolic = '8462-4';
Future<List<Reading>> loadReadings(Anpheros anpheros, String patientId) async {
final page = await anpheros.observations.list(patientId, code: _bloodPressure, limit: 50);
return [
for (final Observation o in page.data)
if (o.effectiveDateTime != null)
Reading(
o.effectiveDateTime!,
o.components.firstWhere((c) => c.code == _systolic).value,
o.components.firstWhere((c) => c.code == _diastolic).value,
),
];
}
Future<void> addReading(Anpheros anpheros, String patientId, double systolic, double diastolic) =>
anpheros.observations.createObservation(
patientId,
ObservationInput(
code: _bloodPressure,
category: 'vital-signs',
effectiveAt: DateTime.now(),
components: [
Component(code: _systolic, value: systolic, unit: 'mm[Hg]'),
Component(code: _diastolic, value: diastolic, unit: 'mm[Hg]'),
],
authorType: AuthorType.patient,
),
sourceSystem: 'bp-app',
);
Okumalar, standart FHIR Observations olarak saklanır, LOINC 85354-9 sistolik ve diyastolik bileşenlerle birlikte, böylece kişinin doktoru, bir klinik sistemi veya bağladıkları başka bir uygulama, sizin uygulamanız hakkında hiçbir bilgi sahibi olmadan bunları okur. authorType: patient kişinin okumayı girdiğini kaydeder. SDK, her oluşturma işlemiyle birlikte bir idempotency anahtarı gönderir, böylece kötü bir bağlantı durumunda yeniden deneme okumayı iki kez eklemez.
Adım 3: Ekran
// lib/main.dart
import 'package:anpheros_sdk/anpheros_sdk.dart';
import 'package:flutter/material.dart';
import 'readings.dart';
import 'record_connection.dart';
void main() => runApp(const MaterialApp(home: ReadingsScreen()));
class ReadingsScreen extends StatefulWidget {
const ReadingsScreen({super.key});
@override
State<ReadingsScreen> createState() => _ReadingsScreenState();
}
class _ReadingsScreenState extends State<ReadingsScreen> {
final _connection = RecordConnection();
Anpheros? _client;
String? _patient;
List<Reading> _readings = [];
String? _error;
@override
void initState() {
super.initState();
_open();
}
Future<void> _open() async {
final opened = await _connection.open();
if (opened == null) return;
setState(() {
_client = opened.$1;
_patient = opened.$2;
});
await _refresh();
}
Future<void> _refresh() async {
try {
final readings = await loadReadings(_client!, _patient!);
setState(() {
_readings = readings;
_error = null;
});
} on AnpherosException catch (e) {
if (e.status == 401) {
await _connection.disconnect(); // access was revoked or has expired
setState(() {
_client = null;
_patient = null;
_readings = [];
});
} else {
setState(() => _error = e.message);
}
}
}
@override
Widget build(BuildContext context) {
if (_client == null) {
return Scaffold(
body: Center(
child: FilledButton(
onPressed: () async {
await _connection.connect();
await _open();
},
child: const Text('Connect my health record'),
),
),
);
}
return Scaffold(
appBar: AppBar(title: const Text('Blood pressure')),
body: RefreshIndicator(
onRefresh: _refresh,
child: ListView(children: [
if (_error != null) ListTile(title: Text(_error!)),
for (final r in _readings)
ListTile(
title: Text('${r.systolic.round()}/${r.diastolic.round()} mmHg'),
subtitle: Text(r.measuredAt.toLocal().toString()),
),
]),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await addReading(_client!, _patient!, 128, 82); // replace with a form
await _refresh();
},
child: const Icon(Icons.add),
),
);
}
}
Bunu flutter run ile çalıştırın, Sağlık kaydımı bağla'ya dokunun, izin ekranında oturum açın, erişime izin verin ve ardından bir okuma ekleyin.
Kapsamlar ne işe yarar
Uygulama patient/Observation.rs?category=vital-signs ve patient/Observation.c istedi. Bu, yalnızca yaşamsal belirtileri okuyabileceği ve gözlemler ekleyebileceği, başka hiçbir şey yapamayacağı anlamına gelir. Kişinin laboratuvar sonuçları, teşhisleri veya belgeleri için bir istek 403'ü yanıtlar ve bu belirteç için başka herhangi bir hasta mevcut değildir (404). Kişi Anpheros'ta erişimi iptal ederse, bir sonraki çağrı 401'ü yanıtlar ve yukarıdaki ekran Connect'e geri döner.
Bir özellik gerektirdiğinde daha fazlasını isteyin, örneğin ilaç listesi için patient/MedicationStatement.rs, ve onay ekranını açmadan önce uygulamanızda nedenini belirtin.
İleriye Gitmek
- Kendi arka ucunuz. Ürününüz aynı zamanda sunucu özelliklerine (hatırlatıcılar, klinisyen panoları) sahipse, TypeScript ile Sağlık API'si Nasıl Oluşturulur bölümüne bakın. API anahtarlarını sunucuda ve OAuth belirteçlerini cihazda saklayın.
- Grafikler ve Yapay Zeka özetleri.
anpheros.context.build, bir model için kaynak etiketli bir bağlam döndürür: FHIR arka ucu ile Yapay Zeka Sağlık Uygulaması Nasıl Oluşturulur. - Ham FHIR.
anpheros.fhir, aynı kayıt üzerinde okuma, arama, oluşturma, işlem,$everythingve$summarysağlar (FHIR R4 Kullanarak Tıbbi Kayıtlar Nasıl Saklanır).
Sıkça Sorulan Sorular
Flutter için bir FHIR SDK'sı var mı?
Evet. pub.dev'deki anpheros_sdk, Anpheros FHIR R4 ve REST API'leri için bir Dart istemcisidir. OAuth 2.1 / SMART on FHIR, PKCE, token yenileme, idempotent yazma ve tipli modelleri destekler. Flutter'da iOS, Android, macOS ve web üzerinde çalışır.
Bir Flutter sağlık uygulaması token'ları nereye kaydetmelidir?
Platformun güvenli depolama alanına (iOS'ta Keychain, Android'de Keystore destekli depolama) flutter_secure_storage gibi bir paket aracılığıyla kaydedilmelidir. Asla paylaşılan tercihlerde veya düz dosyalarda saklanmamalıdır.
Uygulamanın kendi arka ucuna ihtiyacı var mı?
Tıbbi veriler için gerekmez: uygulama, kişinin kaydını doğrudan kendi rızasıyla okur ve yazar. Ürününüzün yaptığı diğer her şey için ve kişilerin adına kayıt oluşturuyorsanız API anahtarlarını tutmak için bir arka uca ihtiyacınız olacaktır.
Gerçek hastalar olmadan test edebilir miyim?
Evet. Özel beta sırasında, OAuth bağlantıları sandbox kayıtlarını kullanır ve sandbox'ta altı aylık geçmişe sahip 30 sentetik hasta bulunur.
İlgili Konular
- SMART on FHIR ile hasta rızasını uygulama
- Sağlık uygulaması oluşturma: API tarafında aynı yapı taşları
- Kimlik Doğrulama ve OAuth
- Tıbbi Veri API'si
- Ücretsiz FHIR veritabanı ve sandbox
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card