Skip to main content

Command Palette

Search for a command to run...

Integrating FHIR with Flutter – Part 1

A Guide to FHIR Introduction and Setting Up Your Project

Updated
10 min read
Integrating FHIR with Flutter – Part 1

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

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 cleanup

  • Provider: Dependency injection

  • Type-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:

  1. Clone the Repository:

    git clone https://github.com/Captured-Heart/fhir_demo.git
    cd fhir_demo
    
  2. Explore FHIR Documentation:

  3. Try HAPI FHIR Server:

  4. Review Flutter Concepts:

    • Form handling

    • State management basics

    • HTTP requests

Resources

FHIR Documentation

FHIR Servers

Flutter Resources