<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mandar Nilange</title>
    <description>The latest articles on DEV Community by Mandar Nilange (@mandar_nilange_03a372dcc1).</description>
    <link>https://dev.to/mandar_nilange_03a372dcc1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2647595%2Fbe257c98-eafa-4657-8cfc-29617fd21c1d.png</url>
      <title>DEV Community: Mandar Nilange</title>
      <link>https://dev.to/mandar_nilange_03a372dcc1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mandar_nilange_03a372dcc1"/>
    <language>en</language>
    <item>
      <title>Building a Lossless Multi-Provider Health Layer in Flutter (Strava, HealthKit, Health Connect, Oura)</title>
      <dc:creator>Mandar Nilange</dc:creator>
      <pubDate>Thu, 30 Apr 2026 13:04:55 +0000</pubDate>
      <link>https://dev.to/mandar_nilange_03a372dcc1/building-a-lossless-multi-provider-health-layer-in-flutter-strava-healthkit-health-connect-oura-673</link>
      <guid>https://dev.to/mandar_nilange_03a372dcc1/building-a-lossless-multi-provider-health-layer-in-flutter-strava-healthkit-health-connect-oura-673</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A deep dive into Health Forge — a federated, zero-backend Flutter toolkit that unifies HealthKit, Health Connect, Oura, Strava, and Garmin without throwing away the metrics each provider is famous for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have ever tried to build a serious health app in Flutter that pulls from more than one provider, you have probably had this exact moment of grief:&lt;/p&gt;

&lt;p&gt;You wire up the popular &lt;code&gt;health&lt;/code&gt; package. It works. You sync sleep from Apple Watch. You sync sleep from Oura. You feel productive. Then you realize Oura's &lt;strong&gt;Readiness Score&lt;/strong&gt; — the single most useful number Oura produces — is gone. Not stored as null. Not flagged as unsupported. Just… not in the model. Because the unified schema doesn't have a slot for it.&lt;/p&gt;

&lt;p&gt;So you start writing per-provider wrappers. Now you have one API for HealthKit, a different one for Strava, a third for Oura's REST API, and your "platform layer" is doing more dispatching than your business logic. One of those wrappers is GPL. Another vendors its own commercial backend. The package that promised to "just work" has metastasized.&lt;/p&gt;

&lt;p&gt;This is the problem &lt;a href="https://github.com/mandarnilange/health_forge" rel="noopener noreferrer"&gt;&lt;strong&gt;Health Forge&lt;/strong&gt;&lt;/a&gt; was built to solve. It is an MIT-licensed, federated, zero-backend Flutter toolkit that aggregates health data from multiple providers into a unified model &lt;strong&gt;without losing the provider-specific metrics&lt;/strong&gt; that make those providers worth integrating in the first place.&lt;/p&gt;

&lt;p&gt;This article is the design walkthrough — the trade-offs, the architecture decisions, and the parts that genuinely surprised me on the way to v0.1.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Repo: &lt;a href="https://github.com/mandarnilange/health_forge" rel="noopener noreferrer"&gt;https://github.com/mandarnilange/health_forge&lt;/a&gt;&lt;br&gt;
Packages on pub.dev: &lt;code&gt;health_forge_core&lt;/code&gt;, &lt;code&gt;health_forge&lt;/code&gt;, &lt;code&gt;health_forge_apple&lt;/code&gt;, &lt;code&gt;health_forge_ghc&lt;/code&gt;, &lt;code&gt;health_forge_oura&lt;/code&gt;, &lt;code&gt;health_forge_strava&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Flutter health-data landscape, honestly
&lt;/h2&gt;

&lt;p&gt;Before I started, I tried very hard to not write yet another health package. The existing options break into three families, and each one fails a different way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it does well&lt;/th&gt;
&lt;th&gt;Where it falls over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The &lt;code&gt;health&lt;/code&gt; package&lt;/td&gt;
&lt;td&gt;One API across HealthKit and Health Connect&lt;/td&gt;
&lt;td&gt;Strips provider-specific metrics. No Oura, Strava, Garmin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Individual provider wrappers&lt;/td&gt;
&lt;td&gt;High fidelity per provider&lt;/td&gt;
&lt;td&gt;Every wrapper has a different API surface, varying maintenance, occasional GPL contamination.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial SDKs (Terra, Vital, Spike)&lt;/td&gt;
&lt;td&gt;Genuinely good DX&lt;/td&gt;
&lt;td&gt;Vendor lock-in, mandatory backend, closed source, monthly bill scaling with your users.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I wanted a fourth option: &lt;strong&gt;a unified model that doesn't lie to you about what the data was, with no backend, MIT-licensed, and federated so I only ship code for the providers I actually use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I wrote one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design goal #1: Don't strip provider metrics. Don't pollute the core either.
&lt;/h2&gt;

&lt;p&gt;This is the central tension. A unified model is the whole point — but the moment you drop Oura's &lt;code&gt;readinessScore&lt;/code&gt; or Strava's &lt;code&gt;sufferScore&lt;/code&gt;, your "unified" app is no better than its lowest-common-denominator schema.&lt;/p&gt;

&lt;p&gt;I solved this with a &lt;strong&gt;type-map extension mechanism&lt;/strong&gt; sitting on top of every record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProviderExtension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;typeKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;mixin&lt;/span&gt; &lt;span class="nc"&gt;HealthRecordMixin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... envelope fields ...&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProviderExtension&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ProviderExtension&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;extension&amp;lt;T&amp;gt;()&lt;/code&gt; getter is the entire payoff. From your app code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;whereType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SleepSession&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Common, normalized fields — works for any provider&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Slept &lt;/span&gt;&lt;span class="si"&gt;${sleep.endTime.difference(sleep.startTime)}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Provider-specific magic, type-safe&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;oura&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OuraSleepExtension&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oura&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Readiness: &lt;/span&gt;&lt;span class="si"&gt;${oura.readinessScore}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Body temp deviation: &lt;/span&gt;&lt;span class="si"&gt;${oura.temperatureDeviation}&lt;/span&gt;&lt;span class="s"&gt;°C'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core schema doesn't know &lt;code&gt;OuraSleepExtension&lt;/code&gt; exists. The Oura package registers it at init time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ProviderExtensionRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'oura_sleep'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;OuraSleepExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what made the difference. The unified &lt;code&gt;SleepSession&lt;/code&gt; is yours. The Oura-flavored bonus data rides along, survives JSON serialization, survives the cache, survives an isolate hop, and &lt;strong&gt;disappears&lt;/strong&gt; from your binary if you don't depend on &lt;code&gt;health_forge_oura&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Trade-off worth naming out loud: extensions aren't compile-time guaranteed. If you forget to register an extension type at app startup, deserialization silently drops it. I considered an annotation-driven registry; for v0.1 I optimized for "tiny core, explicit registration." This will likely tighten in v0.2.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design goal #2: Federate the providers
&lt;/h2&gt;

&lt;p&gt;If you only use Apple Health, you should not pay for Oura's HTTP client, OAuth flow, and rate limiter. Conversely, when Garmin's API has a breaking change, your Apple-only app should not be forced into a coordinated upgrade.&lt;/p&gt;

&lt;p&gt;The dependency graph is enforced as a hard rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;health_forge_core (pure Dart)
        ▲
        │
        │ depended on by
        │
   ┌────┴───────────────────────────────┐
   │                                    │
health_forge (Flutter)         health_forge_{provider}
                                       │
                                       │ never depends on
                                       ▼
                                 each other or on health_forge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;health_forge_core&lt;/code&gt; is pure Dart with &lt;strong&gt;no Flutter imports&lt;/strong&gt;. It runs in an isolate. It runs on a server. It runs in a Dart CLI tool.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;health_forge&lt;/code&gt; is the Flutter client — registry, auth orchestrator, query builder, cache, sync.&lt;/li&gt;
&lt;li&gt;Provider packages depend on &lt;code&gt;health_forge_core&lt;/code&gt; and nothing else from this repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mono-repo is managed with &lt;a href="https://melos.invertase.dev/" rel="noopener noreferrer"&gt;melos&lt;/a&gt;, which means a single command bootstraps everything and runs analyze/test across all eight packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub global activate melos
dart run melos bootstrap
dart run melos run analyze   &lt;span class="c"&gt;# zero warnings required&lt;/span&gt;
dart run melos run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters in practice: when I want to ship the Garmin adapter, I don't need to coordinate a release of &lt;code&gt;health_forge_apple&lt;/code&gt;. I bump &lt;code&gt;health_forge_garmin&lt;/code&gt; and that's it. Independent versioning is a feature.&lt;/p&gt;

&lt;p&gt;The ugly side: cross-package envelope changes (e.g., adding a new &lt;code&gt;Provenance&lt;/code&gt; field) require coordinated releases. The solution there is discipline — bump core, then sweep the providers, then bump the Flutter client. Annoying, but predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design goal #3: Conflict resolution that you can actually audit
&lt;/h2&gt;

&lt;p&gt;Here's a fun problem nobody warns you about: if your user wears an Apple Watch &lt;em&gt;and&lt;/em&gt; an Oura ring, both will report sleep. Both will report heart rate. Sometimes the windows overlap. Sometimes they conflict.&lt;/p&gt;

&lt;p&gt;The naïve answer is "let the user pick a primary device." That's not good enough — Apple Watch is better at workouts, Oura is better at overnight metrics. The right answer depends on the metric.&lt;/p&gt;

&lt;p&gt;Health Forge ships a &lt;code&gt;MergeEngine&lt;/code&gt; with five built-in strategies and an audit trail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;mergeConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;defaultStrategy:&lt;/span&gt; &lt;span class="n"&gt;ConflictStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;priorityBased&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;strategiesByMetric:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleepSession&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ConflictStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;priorityBased&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;heartRate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;ConflictStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mostGranular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;ConflictStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;average&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nl"&gt;providerPriorities:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleepSession&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;DataProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;oura&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;DataProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;DataProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ghc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nl"&gt;timeOverlapThresholdSeconds:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;QueryExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;registry:&lt;/span&gt; &lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;mergeEngine:&lt;/span&gt; &lt;span class="n"&gt;MergeEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;config:&lt;/span&gt; &lt;span class="n"&gt;mergeConfig&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Resolved: &lt;/span&gt;&lt;span class="si"&gt;${result.records.length}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Conflicts: &lt;/span&gt;&lt;span class="si"&gt;${result.conflictReport.entries.length}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;conflictReport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'  &lt;/span&gt;&lt;span class="si"&gt;${entry.metric}&lt;/span&gt;&lt;span class="s"&gt;: dropped &lt;/span&gt;&lt;span class="si"&gt;${entry.dropped.length}&lt;/span&gt;&lt;span class="s"&gt;, '&lt;/span&gt;
        &lt;span class="s"&gt;'kept by &lt;/span&gt;&lt;span class="si"&gt;${entry.strategy}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five strategies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priorityBased&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You know which provider you trust per metric. Most apps live here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;keepAll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You don't want to throw away anything; render with attribution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;average&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aggregate dashboards over numeric metrics like weight or RHR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mostGranular&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prefer 1 Hz heart-rate series over a 5-minute summary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;custom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pass a callback. Domain-specific logic for clinical apps.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The non-negotiable feature: every merge decision lands in a &lt;code&gt;ConflictReport&lt;/code&gt;. You can render it, log it, or surface it to the user. Health data is high-stakes — silent dedup is a bug, not a feature.&lt;/p&gt;

&lt;p&gt;A real lesson from building this: &lt;strong&gt;the merge engine had to be pure Dart, no exceptions.&lt;/strong&gt; I want to run it in an isolate when a query returns thousands of records. The moment a Flutter type sneaks into core, isolate transfer breaks. ADR-0004 ("pure Dart core") sounds dogmatic on paper; on the ground, it's the only thing that lets the merge engine scale.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;custom&lt;/code&gt; strategy is the one place this leaks. A custom callback runs on the calling isolate because closures aren't generally portable across isolates in Dart. That's a documented trade-off, not a bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design goal #4: Make REST adapters as easy as native SDK adapters
&lt;/h2&gt;

&lt;p&gt;Health Forge has two flavors of provider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native SDK adapters&lt;/strong&gt; — &lt;code&gt;health_forge_apple&lt;/code&gt; (HealthKit) and &lt;code&gt;health_forge_ghc&lt;/code&gt; (Health Connect). These bridge to platform code via Pigeon-generated FFI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API adapters&lt;/strong&gt; — &lt;code&gt;health_forge_oura&lt;/code&gt; and &lt;code&gt;health_forge_strava&lt;/code&gt;. These speak HTTP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The risk was that REST adapters would feel like second-class citizens — different auth, different errors, different caching. Instead, every adapter implements the same interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HealthProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;DataProvider&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;providerType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;ProviderCapabilities&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuthResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deauthorize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HealthRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HealthQuery&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The REST adapter pattern (ADR-0007) standardizes the messy parts: OAuth 2.0 with PKCE, a Dio HTTP client wrapped in a rate limiter, paginated streaming, and a token store that persists across launches.&lt;/p&gt;

&lt;p&gt;Strava has the most interesting twist — its public API rate-limits on &lt;strong&gt;two&lt;/strong&gt; dimensions simultaneously (15-minute and daily windows). The &lt;code&gt;DualRateLimiter&lt;/code&gt; is one of the parts of the codebase I'm proudest of, and it's also the kind of thing you would never want to write three times. Putting it in a shared adapter pattern means the next REST provider (Garmin? Whoop? Polar?) gets it for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the application code actually looks like
&lt;/h2&gt;

&lt;p&gt;Enough architecture. Here's the developer-facing surface for an app that wants Apple + Oura sleep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pubspec.yaml — only what you use&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;health_forge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.1.1&lt;/span&gt;
  &lt;span class="na"&gt;health_forge_apple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.1.1&lt;/span&gt;
  &lt;span class="na"&gt;health_forge_oura&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.1.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:health_forge/health_forge.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:health_forge_apple/health_forge_apple.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:health_forge_oura/health_forge_oura.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Register extensions before constructing the client&lt;/span&gt;
  &lt;span class="n"&gt;HealthForgeOura&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;forge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HealthForgeClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppleHealthProvider&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OuraHealthProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;clientId:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'OURA_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;redirectUri:&lt;/span&gt; &lt;span class="s"&gt;'myapp://oauth/oura'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// One call kicks off every provider's auth flow&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizeAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Build a query — fluent, type-safe, isolate-friendly&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forMetrics&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleepSession&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;start:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;days:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
          &lt;span class="nl"&gt;end:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QueryExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;registry:&lt;/span&gt; &lt;span class="n"&gt;forge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;mergeEngine:&lt;/span&gt; &lt;span class="n"&gt;MergeEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;config:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MergeConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;defaultStrategy:&lt;/span&gt; &lt;span class="n"&gt;ConflictStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;priorityBased&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;providerPriorities:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;MetricType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleepSession&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DataProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;oura&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;whereType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SleepSession&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;oura&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OuraSleepExtension&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${session.startTime}&lt;/span&gt;&lt;span class="s"&gt; | '&lt;/span&gt;
          &lt;span class="s"&gt;'duration=&lt;/span&gt;&lt;span class="si"&gt;${session.endTime.difference(session.startTime).inHours}&lt;/span&gt;&lt;span class="s"&gt;h | '&lt;/span&gt;
          &lt;span class="s"&gt;'readiness=&lt;/span&gt;&lt;span class="si"&gt;${oura?.readinessScore ?? "—"}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;health_forge_oura&lt;/code&gt; package brought along its OAuth flow, rate limiter, and extension registration. None of that is in your code.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;extension&amp;lt;T&amp;gt;()&lt;/code&gt; call is null-safe because Apple records won't have an &lt;code&gt;OuraSleepExtension&lt;/code&gt;. The print statement gracefully degrades.&lt;/li&gt;
&lt;li&gt;The merge engine ran on a normal isolate-safe code path; on a query that returns thousands of samples you can hand the merge step off entirely.&lt;/li&gt;
&lt;li&gt;Caching is opt-in via &lt;code&gt;CacheManager&lt;/code&gt;. The default is in-memory; the Drift-backed &lt;code&gt;DriftCacheManager&lt;/code&gt; gives you SQLite-backed offline-first behavior with no extra server. ADR-0006 covers the schema if you're curious.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I learned that wasn't on the architecture diagrams
&lt;/h2&gt;

&lt;p&gt;A few things only became clear after the code was running:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;freezed&lt;/code&gt; and inheritance don't mix, but mixins are fine.&lt;/strong&gt; I started by trying to give every record a base class with the envelope fields. &lt;code&gt;freezed&lt;/code&gt; generates classes that can't extend other &lt;code&gt;freezed&lt;/code&gt; classes. The fix was a mixin (&lt;code&gt;HealthRecordMixin&lt;/code&gt;) with abstract getters that each &lt;code&gt;freezed&lt;/code&gt; factory implements. Slight boilerplate per record, but full code-gen compatibility and no inheritance lock-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Zero-backend means *zero backend.&lt;/strong&gt;* I had to talk myself out of "but what if we just had a tiny edge function for token refresh…" several times. Every concession to a backend changes the licensing story, the privacy story, and the "MIT toolkit you can ship in a paid app" story. Tokens live in &lt;code&gt;flutter_secure_storage&lt;/code&gt;. Refreshes happen on-device. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Coverage gates are load-bearing.&lt;/strong&gt; The repo enforces ≥90% line coverage per package in CI (excluding generated files). When I refactored the merge engine in week three, that gate caught two regressions a more permissive threshold would have missed. For a library where downstream consumers can't easily fork, the test suite &lt;em&gt;is&lt;/em&gt; the API contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. ADRs paid for themselves twice.&lt;/strong&gt; When I came back to the project after a two-week gap, the seven ADRs in &lt;code&gt;design/adr/&lt;/code&gt; reminded me why the core was pure Dart, why extensions used a type map instead of inheritance, and why the cache schema was denormalized. I almost talked myself into "fixing" each of those decisions before reading the rationale.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Status as of v0.1.1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stable — 21 record types, merge engine, 5 strategies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stable — registry, auth, queries, in-memory + Drift cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_apple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device-tested on iOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_ghc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device-tested on Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_oura&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code-complete, fully unit-tested, awaiting end-to-end testing against live API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_strava&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code-complete, fully unit-tested, awaiting end-to-end testing against live API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;health_forge_garmin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Next on the roadmap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The example app in &lt;code&gt;example/&lt;/code&gt; is a working Flutter dashboard with real OAuth flows for Oura and Strava (via deep links), mock providers for desktop development, a 10-card metric dashboard, and a query/browse screen.&lt;/p&gt;

&lt;p&gt;If any of this is useful to you, I'd love to hear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What providers do you actually need? (Polar, Whoop, Withings have all been suggested.)&lt;/li&gt;
&lt;li&gt;Do the conflict-resolution strategies cover your use case, or do you need something the &lt;code&gt;custom&lt;/code&gt; callback can't express?&lt;/li&gt;
&lt;li&gt;How does your team feel about running the merge engine on a background isolate vs. on the main one?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/mandarnilange/health_forge" rel="noopener noreferrer"&gt;https://github.com/mandarnilange/health_forge&lt;/a&gt;. Issues, PRs, and "your design here is wrong because…" comments are all genuinely welcome. The contributor guide enforces TDD, zero analyzer warnings, and an ADR for any architecture-level change — same rules I held myself to.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you build something with Health Forge — or if it makes you want to build a competing thing — drop a comment. I learn more from the second category than the first.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>opensource</category>
      <category>healthtech</category>
    </item>
    <item>
      <title>Open Sourcing Health Tracker Reports: A Privacy-First Health App Built with AI Assistance</title>
      <dc:creator>Mandar Nilange</dc:creator>
      <pubDate>Sun, 26 Oct 2025 16:25:13 +0000</pubDate>
      <link>https://dev.to/mandar_nilange_03a372dcc1/open-sourcing-health-tracker-reports-a-privacy-first-health-app-built-with-ai-assistance-5hio</link>
      <guid>https://dev.to/mandar_nilange_03a372dcc1/open-sourcing-health-tracker-reports-a-privacy-first-health-app-built-with-ai-assistance-5hio</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I'm open-sourcing a complete Flutter health tracking app built primarily through AI-assisted development. This project serves as both a useful privacy-focused tool and a real-world exploration of AI's capabilities in production software development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What &amp;amp; Why&lt;/li&gt;
&lt;li&gt;
The AI-Assisted Development Journey

&lt;ul&gt;
&lt;li&gt;By The Numbers&lt;/li&gt;
&lt;li&gt;What I Learned&lt;/li&gt;
&lt;li&gt;The Framework&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Technical Highlights&lt;/li&gt;

&lt;li&gt;Key Features&lt;/li&gt;

&lt;li&gt;

Get Involved

&lt;ul&gt;
&lt;li&gt;Try It Yourself&lt;/li&gt;
&lt;li&gt;How You Can Contribute&lt;/li&gt;
&lt;li&gt;Roadmap&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Show Your Support&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  What &amp;amp; Why
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mandarnilange/health_tracker_reports" rel="noopener noreferrer"&gt;Health Tracker Reports&lt;/a&gt;&lt;/strong&gt; is a privacy-first Flutter app for tracking medical reports and daily vitals. Upload PDFs for AI-powered biomarker extraction, log daily vitals, visualize trends, and generate shareable summaries - all while keeping your sensitive health data stored locally on your device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=GFM1JJVlt7I" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezvgdpvkw9cklehuwv1t.jpg" alt="Watch the demo" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=GFM1JJVlt7I" rel="noopener noreferrer"&gt;📺 Watch the App in Action&lt;/a&gt; | &lt;a href="https://github.com/mandarnilange/health_tracker_reports" rel="noopener noreferrer"&gt;⭐ GitHub Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;This project was born from two key motivations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Privacy-First Health Tracking&lt;/strong&gt;: Most health apps either send your data to the cloud (privacy concerns), lack comprehensive features, or don't integrate lab reports with daily tracking. Health Tracker fills this gap with a privacy-first approach where sensitive health data never leaves your device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Exploring AI-Assisted Development&lt;/strong&gt;: The primary goal was to validate whether AI agents can handle &lt;strong&gt;production-ready software development&lt;/strong&gt; - not just code snippets, but a complete, tested, architected application following best practices.&lt;/p&gt;

&lt;p&gt;After experimenting with smaller projects (login screens, REST backends), I needed a real-world test case. A personal need to track medical reports and vitals at home provided the perfect opportunity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler&lt;/strong&gt;: AI agents can build production-ready software, with the right framework and human oversight.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI-Assisted Development Journey
&lt;/h2&gt;

&lt;p&gt;This entire app was built primarily through AI-assisted development, following strict engineering principles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Approach&lt;/strong&gt;: This is neither "vibe coding" nor the other extreme of rigid spec-driven development - I've been experimenting to get the best out of AI assistance while maintaining confidence in the generated code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Timeline&lt;/strong&gt;: About 16 hours of active development time (spread across a week) - what would typically take weeks without AI assistance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Journey&lt;/strong&gt;: I started experimenting with smaller apps, login screens, and REST backends. But a real need at home to track medical reports and vitals led to this comprehensive project.&lt;/p&gt;

&lt;h3&gt;
  
  
  By The Numbers
&lt;/h3&gt;

&lt;p&gt;📊 &lt;strong&gt;Project Metrics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;865+ passing tests&lt;/strong&gt; (98.6% pass rate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;69% overall code coverage&lt;/strong&gt; (90%+ on core layers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% TDD adherence&lt;/strong&gt; on domain/data layers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~15,000 lines of production code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~10,000 lines of test code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 architecture violations&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~16 hours&lt;/strong&gt; of active development vs. weeks traditionally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I Learned
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI agents excel at&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Following strict TDD workflows when properly constrained&lt;/li&gt;
&lt;li&gt;✅ Implementing clean architecture patterns consistently&lt;/li&gt;
&lt;li&gt;✅ Generating comprehensive test suites&lt;/li&gt;
&lt;li&gt;✅ Maintaining documentation (CHANGELOG, commits, code comments)&lt;/li&gt;
&lt;li&gt;✅ Replicating patterns across the codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Humans remain essential for&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 Architecture decisions (which technologies to use)&lt;/li&gt;
&lt;li&gt;🎨 UX/UI design choices&lt;/li&gt;
&lt;li&gt;⚖️ Trade-off analysis (privacy vs features)&lt;/li&gt;
&lt;li&gt;🔍 Strategic oversight and integration testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Framework
&lt;/h3&gt;

&lt;p&gt;I developed a structured framework for AI-assisted development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context Documentation&lt;/strong&gt;: 1,600+ line &lt;code&gt;AGENTS.md&lt;/code&gt; with patterns and examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Specifications&lt;/strong&gt;: Atomic task breakdowns in &lt;code&gt;spec/&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Constraints&lt;/strong&gt;: TDD-first, clean architecture, coverage minimums&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Loops&lt;/strong&gt;: The right checkpoints to maintain quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple AI Tools&lt;/strong&gt;: Gemini Code Assist, GitHub Copilot, Claude, OpenAI Codex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This balanced approach avoids both extremes - it's neither "vibe coding" (hoping AI figures it out) nor rigid waterfall specs (over-constraining creativity).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'll be sharing detailed insights on this framework in upcoming articles&lt;/strong&gt;, covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to structure projects for AI effectiveness&lt;/li&gt;
&lt;li&gt;Prompt engineering for production code&lt;/li&gt;
&lt;li&gt;The human-AI division of labor&lt;/li&gt;
&lt;li&gt;Real examples of what worked and what didn't&lt;/li&gt;
&lt;li&gt;Lessons learned from 15,000+ lines of AI-assisted code&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;: Built with &lt;strong&gt;Clean Architecture&lt;/strong&gt; principles&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain Layer&lt;/strong&gt;: Pure Dart business logic, entities, and use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Layer&lt;/strong&gt;: Repository implementations, Hive persistence, LLM integrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presentation Layer&lt;/strong&gt;: Flutter UI with Riverpod state management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter 3.x • Riverpod • Hive (100% offline) • go_router&lt;/li&gt;
&lt;li&gt;Multi-LLM support (Claude 3.5 Sonnet, GPT-4, Gemini)&lt;/li&gt;
&lt;li&gt;fl_chart • mocktail • get_it + injectable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Technical Achievements&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multi-LLM Vision API Integration&lt;/strong&gt;: Supports Claude, OpenAI, and Gemini for 95%+ extraction accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Biomarker Normalization&lt;/strong&gt;: LLM intelligently maps variations ("Hb" → "Hemoglobin") using your historical data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified Timeline&lt;/strong&gt;: Seamlessly combines lab reports and daily vitals chronologically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medical-Grade Reference Ranges&lt;/strong&gt;: Smart defaults for all vital types with status indicators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual-Line Charts&lt;/strong&gt;: Advanced charting for blood pressure trends (systolic + diastolic)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;📄 PDF Report Upload&lt;/strong&gt;: Upload medical reports and extract biomarkers automatically using AI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💉 Comprehensive Vital Tracking&lt;/strong&gt;: Log blood pressure, SpO2, heart rate, temperature, weight, glucose, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📊 Interactive Trend Analysis&lt;/strong&gt;: Visualize health metrics over time with beautiful charts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📋 Unified Timeline&lt;/strong&gt;: See lab reports and daily vitals in one chronological view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔒 100% Privacy&lt;/strong&gt;: All data stored locally on your device using Hive - no cloud uploads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔑 Bring Your Own LLM&lt;/strong&gt;: Use your own API keys (Claude, GPT-4, or Gemini) for data extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📤 Export &amp;amp; Share&lt;/strong&gt;: Generate professional PDF summaries for doctors or export raw CSV data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎨 Modern UI&lt;/strong&gt;: Material Design 3 with light/dark mode support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Platforms&lt;/strong&gt;: iOS ✅ | Android ✅ | Web 🚧&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;I'm open-sourcing this project to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Help others&lt;/strong&gt; who want privacy-focused health tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share learnings&lt;/strong&gt; about AI-assisted development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborate&lt;/strong&gt; on improving both the app and the AI development framework&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Try It Yourself
&lt;/h3&gt;

&lt;p&gt;💻 &lt;strong&gt;Get started&lt;/strong&gt;: Full setup instructions in the &lt;a href="https://github.com/mandarnilange/health_tracker_reports#getting-started" rel="noopener noreferrer"&gt;GitHub README&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📖 &lt;strong&gt;Explore the AI framework&lt;/strong&gt;: Read &lt;code&gt;AGENTS.md&lt;/code&gt; for the complete context documentation and patterns&lt;/p&gt;

&lt;h3&gt;
  
  
  How You Can Contribute
&lt;/h3&gt;

&lt;p&gt;🤝 &lt;strong&gt;We Welcome Contributions In&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Development&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android platform testing and optimization&lt;/li&gt;
&lt;li&gt;OpenAI/Anthropic LLM integration testing&lt;/li&gt;
&lt;li&gt;PDF export format improvements&lt;/li&gt;
&lt;li&gt;Accessibility enhancements&lt;/li&gt;
&lt;li&gt;Localization support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI Development Framework&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share your AI-assisted development experiences&lt;/li&gt;
&lt;li&gt;Suggest improvements to &lt;code&gt;AGENTS.md&lt;/code&gt; patterns&lt;/li&gt;
&lt;li&gt;Document what works/doesn't in your projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD pipeline setup (GitHub Actions)&lt;/li&gt;
&lt;li&gt;Automated testing and releases&lt;/li&gt;
&lt;li&gt;App store deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ways to Engage&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star the Repo&lt;/strong&gt;: Help others discover this project&lt;/li&gt;
&lt;li&gt;🐛 &lt;strong&gt;Report Issues&lt;/strong&gt;: &lt;a href="https://github.com/mandarnilange/health_tracker_reports/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; for bugs or suggestions&lt;/li&gt;
&lt;li&gt;🔀 &lt;strong&gt;Fork &amp;amp; Experiment&lt;/strong&gt;: Use this as a template for your own AI-assisted projects&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Discuss&lt;/strong&gt;: Share your thoughts on AI-assisted development&lt;/li&gt;
&lt;li&gt;📖 &lt;strong&gt;Read the Code&lt;/strong&gt;: Check out the clean architecture implementation&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Connect&lt;/strong&gt;: Reach out on &lt;a href="https://www.linkedin.com/in/mandarnilange/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for discussions on AI-assisted development&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Roadmap
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technical Enhancements&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📱 Publish to App Store and Play Store&lt;/li&gt;
&lt;li&gt;🔬 Local LLM support for enhanced privacy&lt;/li&gt;
&lt;li&gt;🌐 Web platform support&lt;/li&gt;
&lt;li&gt;📈 Advanced analytics and insights&lt;/li&gt;
&lt;li&gt;🏥 Integration with health data standards (HL7, FHIR)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Functional Features&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The sky's the limit! Your creativity determines what's next :)&lt;/li&gt;
&lt;li&gt;Open to suggestions and contributions from the community&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Show Your Support
&lt;/h2&gt;

&lt;p&gt;If you find this project valuable - whether as a health tracking tool or as an example of AI-assisted development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Star the repository&lt;/strong&gt; on GitHub&lt;/li&gt;
&lt;li&gt;📢 &lt;strong&gt;Share with others&lt;/strong&gt; who might benefit&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Join the discussion&lt;/strong&gt; in issues and discussions&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Contribute&lt;/strong&gt; to the codebase or documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every star, issue, and contribution helps validate this approach and improve the framework.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mandarnilange/health_tracker_reports" rel="noopener noreferrer"&gt;⭐ Star on GitHub&lt;/a&gt; | &lt;a href="https://www.youtube.com/watch?v=GFM1JJVlt7I" rel="noopener noreferrer"&gt;📺 Watch Demo&lt;/a&gt; | &lt;a href="https://github.com/mandarnilange/health_tracker_reports/blob/main/README.md" rel="noopener noreferrer"&gt;📖 Read the Docs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Interested in AI-assisted development? Have experiences to share? Let's discuss in the comments, open an issue on GitHub, or &lt;a href="https://www.linkedin.com/in/mandarnilange/" rel="noopener noreferrer"&gt;connect on LinkedIn&lt;/a&gt;. Stay tuned for upcoming articles diving deep into the framework and lessons learned.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Built with&lt;/strong&gt; ❤️ &lt;strong&gt;using Flutter and AI assistance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PS: Article written by AI assistant and reviewed by me :)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>healthtracker</category>
      <category>developerproductivity</category>
    </item>
  </channel>
</rss>
