Integrating FHIR with Flutter – Part 1
A Guide to FHIR Introduction and Setting Up Your Project

1. Introduction
In today's healthcare sector, transfer and access of patient data remain frustratingly inconsistent across different systems, hospitals, and clinics. A patient who visits multiple healthcare providers often finds their medical history scattered across incompatible electronic health records (EHR) systems. Doctors can't easily access critical information, leading to repeated tests, medication errors, and fragmented care. Also, software developers who wish to build on existing healthcare solutions find it extremely difficult when data is poorly structured, inconsistently labeled, or locked behind proprietary formats. Even when access is granted, the lack of a common data model makes integrating systems slow and expensive.
This is where FHIR (Fast Healthcare Interoperability Resources) comes in—a modern standard that promises to break down these data models and enable seamless healthcare information exchange.
My Journey
I’ve always been drawn to building mobile products that make a direct impact on human lives. In 2025, that drive led me to work with ArewaCare, a medical software company creating digital tools for pregnant women and the physicians who care for them. While working on healthcare workflows, we stumbled on a challenge that quietly underpins almost every medical system: Data fragmentation. “Data fragmentation is a term widely used in the field of data management and particularly in databases and file systems. It is a method of storing a single file or a single piece of data into multiple parts on the disk". Patient records are often scattered across multiple locations, and systems struggle to communicate with one another, which also leads to degradation and increased security risks. I was introduced to the concept of interoperability of data as a solution, and for the first time, I heard the word “FHIR”. This led me to research and build a demo FHIR-compliant healthcare management application using Flutter—and in this two-part series, I'll share everything I learned.
P.S: The very first mobile application I built in 2020 was a healthcare solution - Beata Medrek (An Electronic healthcare record software with an emergency feature that helps one locate the nearest hospital). A solution that was inspired by my first-hand experience with Nigeria's public hospitals while I was taking care of my then-sick mother. She passed gloriously in 2021 🕊️🕊️(pray St. Cordelia Nkpozi soul rest in peace. Amen); and my solution, although completed then, died as well in my GitHub account 😂😂
What You'll Learn in Part 1
Understanding FHIR and why it matters in healthcare
The problem FHIR solves in healthcare interoperability
Overview of a complete healthcare app built with Flutter, key features and use cases
Domain models and entities structure
Libraries and tools for FHIR development
Foundation for Part 2's implementation
What to Expect in Part 2
Complete implementation with code examples
Network layer and API integration
State management with Riverpod
Form handling and validation
Testing strategies
Deployment considerations
Target Audience
Flutter developers curious about healthcare apps
Healthcare IT professionals interested in mobile development
Developers learning about FHIR integration
Students exploring health tech
2. FHIR - Fast Healthcare Interoperability Resources
FHIR (Fast Healthcare Interoperability Resources) is a next-generation standards framework created by HL7 International for exchanging healthcare information electronically and consistently between systems. Think of it as the "REST API for healthcare data."
Key Characteristics
RESTful Architecture
Uses standard HTTP methods (GET, POST, PUT, DELETE)
URL-based resource access
Resource-Based Model
FHIR organizes healthcare data into modular "Resources":
Patient: Demographics, contact information
Observation: Vital signs, lab results
MedicationRequest: Prescriptions
Condition: Diagnoses
Appointment: Scheduling information
DiagnosticReport: Lab test results
Practitioner: Healthcare providers
and many other resources; see a Swagger view of other possible resources: HAPI FHIR Test/Demo Server R4 Endpoint
Each resource is a self-contained unit of healthcare information with a standardized structure.
Modern Data Formats
JSON: Primary format (developer-friendly)
XML: Alternative format
Easy to parse and generate
Interoperability
Enables different healthcare systems to communicate
Reduces integration complexity
Supports mobile and cloud applications
Industry-wide adoption
Problem Solved: Both patients, doctors, and hospitals can access health data on any device.
Use Cases:
View medical records on smartphones
Share data with new doctors
Track vital signs over time
Medication reminders
So a patient can visit ABC hospital and is eventually transferred to XYZ medical center; the latter can easily access all tests, diagnoses, vital signs, history, etc., just by querying the FHIR server of the former. Amazing right? 🥳. In case of an emergency, this tool would be helpful for doctors to make informed decisions.
3. Project Overview & Features
What We're Building
A demo FHIR-compliant healthcare management application built with Flutter that demonstrates real-world integration with public FHIR servers (HAPI, KODJIN, FIREFLY). The application supports patient registration and record management, diagnosis tracking, prescription and medication orders, vital signs and clinical observations, appointment scheduling, and laboratory results reporting.
From a Flutter perspective, the focus wasn’t on UI complexity but on modeling healthcare data as domain objects, mapping them cleanly to FHIR resources, and building predictable data flows for creating, reading, and updating clinical records.
Links to the public servers:
HAPI FHIR (https://hapi.fhir.org/baseR4), Kodjin (https://demo.kodjin.com), Firefly (https://server.fire.ly)
Architecture Overview
The project follows Clean Architecture principles with two main layers:
Domain Layer (Core Business Logic && Implementation)
↓
Presentation Layer (UI)
Don't ask about the data layer 😔. I found it too cumbersome for this small project, so all implementation and use cases happen in the domain repository.
In this section, we'll focus on the Domain Layer, which contains our business entities and models.
Core Entities Explained
1. ApiResponse Entity
Purpose: Standardizes all API responses for consistent error handling
class ApiResponse<T> {
final bool isSuccess;
final T? data;
final String? error;
final int? statusCode;
const ApiResponse({
required this.isSuccess,
this.data,
this.error,
this.statusCode,
});
// Factory constructors for success and error cases
factory ApiResponse.success(T data) {
return ApiResponse(
isSuccess: true,
data: data,
);
}
factory ApiResponse.error(String error, {int? statusCode}) {
return ApiResponse(
isSuccess: false,
error: error,
statusCode: statusCode,
);
}
// Safe data access
T get safeData => data as T;
}
Why This Matters:
Type-safe response handling
Consistent error management
2. Other project FHIR-related Entities (Patient, Observation, MedicationRequest, Condition, Appointment, DiagnosticReport, Practitioner)
There are models from the fhir_r4 plugin (an Official FHIR R4 implementation for Dart on pub.dev (fhir_r4)), but they can be very complex to work with; therefore, I created an entity that complements the plugin's models.
For Patient Entity:
class ProjectPatientEntity {
final String id;
final String firstName;
final String lastName;
final DateTime dateOfBirth;
final String? gender;
final String phoneNumber;
final String? email;
final String? address;
final String? emergencyContactNo;
ProjectPatientEntity({
required this.id,
required this.firstName,
required this.lastName,
required this.dateOfBirth,
this.gender,
required this.phoneNumber,
this.email,
this.address,
this.emergencyContactNo,
});
In the app, the parameters of ProjectPatientEntity are intentionally designed to mirror the input fields in the Register Patient screen. This keeps the Flutter form layer simple and predictable, allowing user input to be captured as a plain domain object without any direct dependency on FHIR.
Before persisting data, this domain entity is then mapped into a FHIR Patient resource. At this boundary, application-specific fields—such as names, contact details, gender, and date of birth—are translated into their standardized FHIR equivalents. This extra mapping step is deliberate: it decouples the Flutter UI and business logic from the FHIR schema while ensuring that any data sent to the server conforms strictly to interoperability standards.
import 'package:fhir_r4/fhir_r4.dart' as fhir;
MapStringDynamic addPatient({fhir.Patient? existingPatient}) {
final body = fhir.Patient(
id: fhir.FhirString(id),
active: fhir.FhirBoolean(true),
name: [
HumanName(family: lastName.toFhirString, given [firstName.toFhirString]),
],
identifier: [fhir.Identifier(value: ApiConstants.projectIdentifier.toFhirString)],
birthDate: dateOfBirth.toFhirDate,
gender: gender != null ? fhir.AdministrativeGender(gender?.toLowerCase()) : null,
telecom: [
fhir.ContactPoint(system: fhir.ContactPointSystem.email, value: email?.toFhirString),
fhir.ContactPoint(system: fhir.ContactPointSystem.phone, value: phoneNumber.toFhirString),
fhir.ContactPoint(system: fhir.ContactPointSystem.other, value: emergencyContactNo?.toFhirString),
],
address: address != null ? [fhir.Address(text: address?.toFhirString)] : null,
);
// I use the .copyWith() when i am editing an already existing patient data
if (existingPatient != null) {
final updatedPatient = existingPatient.copyWith(
name: body.name,
birthDate: body.birthDate,
gender: body.gender,
telecom: body.telecom,
address: body.address,
active: body.active,
);
return updatedPatient.toJson();
}
return body.toJson();
}
For Prescription Entity:
class ProjectPrescriptionEntity {
final String patientId;
final String medication;
final int dosage;
final String route;
final int frequency;
final int duration;
final DateTime startDate;
final String prescribingDoctor;
final String? instructions;
ProjectPrescriptionEntity({
required this.patientId,
required this.medication,
required this.dosage,
required this.route,
required this.frequency,
required this.duration,
required this.startDate,
required this.prescribingDoctor,
this.instructions,
});
// Convert to FHIR MedicationRequest
Map<String, dynamic> addPrescription() {
return MedicationRequest(
status: MedicationrequestStatus.active,
intent: MedicationrequestIntent.order,
subject: Reference(
reference: 'Patient/$patientId'.toFhirString,
),
medicationCodeableConcept: CodeableConcept(
text: medication.toFhirString,
),
authoredOn: startDate.toFhirDateTime,
requester: Reference(
display: prescribingDoctor.toFhirString,
),
dosageInstruction: [
Dosage(
text: instructions?.toFhirString,
timing: Timing(
repeat: TimingRepeat(
frequency: frequency.toFhirPositiveInt,
period: 1.toFhirDecimal,
periodUnit: 'd'.toFhirString,
duration: duration.toFhirDecimal,
durationUnit: 'd'.toFhirString,
),
),
route: CodeableConcept(text: route.toFhirString),
doseAndRate: [
DosageDoseAndRate(
doseQuantity: Quantity(
value: dosage.toFhirDecimal,
unit: 'mg'.toFhirString,
),
),
],
),
],
).toJson();
}
}
The same method goes for all the other entities in the project. Check the Entities folder in the project: lib/src/domain/entities - Source Code (Github)
4. Libraries and tools for FHIR development
Core Dependencies
1. fhir_r4: A Dart implementation of the FHIR (Fast Healthcare Interoperability Resources) R4 specification
dependencies:
fhir_r4: ^0.4.3
Features:
Complete FHIR R4 resource models
Type-safe resource creation
JSON/YAML serialization
Validation support
Extension methods
2. flutter_riverpod: A reactive caching and data-binding framework
dependencies:
flutter_riverpod: ^2.6.1
Key Features for Our App:
AutoDisposeNotifier: Automatic controller cleanupProvider: Dependency injectionType-safe state access
P.S: I am not a big fan of being loyal to a state management solution; you can use any that works! 😅😅
3. Dio: Powerful HTTP client for Dart
dependencies:
dio: ^5.9.0
Key Features:
Base URL configuration
Custom headers (FHIR requires
application/fhir+json)Retry mechanism
Progress callbacks
4. dio_smart_retry: Automatic retry for failed requests
dependencies:
dio_smart_retry: ^7.0.1
Features:
Exponential backoff
Configurable retry count
Specific error type retry
Configuration:
dio.interceptors.add(
RetryInterceptor(
dio: dio,
retries: 3,
retryDelays: [
Duration(seconds: 1),
Duration(seconds: 3),
Duration(seconds: 5),
],
),
);
What's Coming in Part 2: Implementation
Part 2 will dive deep into the actual implementation:
1. Project Setup & Configuration
Installing dependencies
Platform-specific configuration
2. Network Layer Implementation
Dio configuration and setup
FHIR server integration
Interceptors (logging, retry, auth)
Dynamic server switching
Error handling
3. Repository Pattern
Abstract repositories
Implementation with dependency injection
CRUD operations
API response handling
4. State Management with Riverpod
Controller setup
Form state management
Loading states
Error handling
Provider dependency injection
5. Building FHIR Forms
Complete implementation of:
Patient registration form
Prescription form with validation
Observations form
Converting form data to FHIR resources
Submitting to the FHIR server
Handling responses
Prepare for Part 2
Recommended Actions:
Clone the Repository:
git clone https://github.com/Captured-Heart/fhir_demo.git cd fhir_demoExplore FHIR Documentation:
Try HAPI FHIR Server:
Visit: https://hapi.fhir.org/baseR4
Explore existing resources
Try creating a Patient via Swagger UI
Review Flutter Concepts:
Form handling
State management basics
HTTP requests
Resources
FHIR Documentation
FHIR Servers
Flutter Resources
Project Links
Part 2 (Coming Soon!)


