<?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: Jhin Lee</title>
    <description>The latest articles on DEV Community by Jhin Lee (@leehack).</description>
    <link>https://dev.to/leehack</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F952098%2F87e9abb1-4dc5-4cf7-988f-a47aeaea919b.png</url>
      <title>DEV Community: Jhin Lee</title>
      <link>https://dev.to/leehack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leehack"/>
    <language>en</language>
    <item>
      <title>Local speech with llamadart: giving Llama Village a voice</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Wed, 14 Oct 2026 03:03:34 +0000</pubDate>
      <link>https://dev.to/gde/local-speech-with-llamadart-giving-llama-village-a-voice-5b7m</link>
      <guid>https://dev.to/gde/local-speech-with-llamadart-giving-llama-village-a-voice-5b7m</guid>
      <description>&lt;p&gt;&lt;em&gt;Beyond chat with llamadart · Part 3&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pip’s scarf is missing. I wanted Dash to be able to fly over to Mo and ask about it out loud.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Dash:&lt;/strong&gt; “Mo, have you seen Pip’s red scarf?”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mo:&lt;/strong&gt; “Yes, it's in the reeds beside the pond.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tried that exchange with local models. For now, it runs in a small command-line program outside &lt;a href="https://dev.to/gde/llama-village-a-virtual-world-powered-by-local-ai-with-llamadart-3lmi"&gt;Llama Village&lt;/a&gt;. The question is a synthetic recording, and Mo gets the scarf’s location in his prompt. The game does not have voice controls yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo1pmkf0fajfapzc2ixu.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo1pmkf0fajfapzc2ixu.jpg" alt="Llama Village at golden hour" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The existing village. I’d like to let the player speak to these characters.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ways for Mo to listen
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqqp4ocbykq6yus4rbdwb.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqqp4ocbykq6yus4rbdwb.png" alt="Two routes from speech to a spoken reply" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ASR is speech recognition. TTS is text to speech. Both routes finish with TTS. Concept illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Via text:&lt;/strong&gt; Qwen3-ASR transcribes the recording, a dialogue model answers, and Qwen3-TTS speaks. Having a transcript helps when “Pip” is misheard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct audio:&lt;/strong&gt; Qwen2.5-Omni receives the recording and writes an answer. This example has no separate transcript. The &lt;a href="https://huggingface.co/ggml-org/Qwen2.5-Omni-3B-GGUF" rel="noopener noreferrer"&gt;GGUF conversion&lt;/a&gt; accepts audio but does not generate it, so I still use TTS for Mo’s reply.&lt;/p&gt;

&lt;p&gt;For the comparison, both routes use the same 3B dialogue model. In the first route it receives text; in the second it also loads its audio projector. This is one tested multimodal route, not a claim that all multimodal models need separate TTS.&lt;/p&gt;

&lt;p&gt;I also tried a smaller pair for the text route: &lt;a href="https://huggingface.co/Qwen/Qwen3.5-0.8B" rel="noopener noreferrer"&gt;Qwen3.5 0.8B&lt;/a&gt; for dialogue and &lt;a href="https://huggingface.co/Qwen/Qwen3-TTS-12Hz-0.6B-Base" rel="noopener noreferrer"&gt;Qwen3-TTS 0.6B Base&lt;/a&gt; for speech. Qwen3-ASR 0.6B still handles recognition. The smaller pair is the default below; &lt;code&gt;staged&lt;/code&gt; keeps the original 3B/1.7B pair for comparison. Qwen3.5 is used for text here, not audio input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your Mac
&lt;/h2&gt;

&lt;p&gt;The complete example is below. You need &lt;strong&gt;macOS, Dart 3.10.7 or newer, and about 2.31 GB for the five small-model files&lt;/strong&gt;, plus space for Dart packages and the native runtime. I tested on an M4 Max with Dart 3.13.5 and &lt;strong&gt;llamadart 0.11.0&lt;/strong&gt;. The commands use macOS &lt;code&gt;say&lt;/code&gt;, &lt;code&gt;afconvert&lt;/code&gt;, and &lt;code&gt;afplay&lt;/code&gt;; they are not a phone or browser demo.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the project
&lt;/h3&gt;

&lt;p&gt;Run this in a terminal. The Dart program uses pinned &lt;code&gt;ModelSource.huggingFace&lt;/code&gt; references. Each engine’s &lt;code&gt;load()&lt;/code&gt; uses llamadart’s download manager to fetch missing files and reuse its cache. There is no separate curl script. The first run needs a network connection and takes longer; subsequent generation uses the downloaded models locally.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Copy the setup commands
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; llama-voice-demo/bin
&lt;span class="nb"&gt;cd &lt;/span&gt;llama-voice-demo
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pubspec.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
name: llama_voice_demo
publish_to: none
environment:
  sdk: ^3.10.7
dependencies:
  llamadart: 0.11.0
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;dart pub get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add the complete program
&lt;/h3&gt;

&lt;p&gt;Stay in &lt;code&gt;llama-voice-demo&lt;/code&gt; and paste this block. It creates &lt;code&gt;bin/voice.dart&lt;/code&gt;. The program loads one engine at a time and disposes it before loading the next.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Copy the full Dart program
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bin/voice.dart &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;DART&lt;/span&gt;&lt;span class="sh"&gt;'
import 'dart:io';

import 'package:llamadart/llamadart.dart';

const params = ModelParams(
  contextSize: 4096,
  numberOfThreads: 4,
  numberOfThreadsBatch: 4,
);

const sources = {
  'asr': (
    'ggml-org/Qwen3-ASR-0.6B-GGUF',
    '928ab958557df9aa2ef1c93e0e83c7ad0933fae2',
  ),
  'omni': (
    'ggml-org/Qwen2.5-Omni-3B-GGUF',
    '75f1b73b657a50f5092502799457ccb4a4a1f9df',
  ),
  'dialogue': (
    'unsloth/Qwen3.5-0.8B-GGUF',
    '6ab461498e2023f6e3c1baea90a8f0fe38ab64d0',
  ),
  'tts': (
    'ggml-org/Qwen3-TTS-12Hz-1.7B-Base-GGUF',
    'ca27d74bc954b73dadab5b71ca265d87fc861a7c',
  ),
  'smallTts': (
    'mradermacher/Qwen3-TTS-12Hz-0.6B-Base-GGUF',
    '0762b31e9cd99274d49dca4e895bd81863134f08',
  ),
};

ModelSource model(String family, String file) {
  final (repo, revision) = sources[family]!;
  return ModelSource.huggingFace(
    repoId: repo,
    revision: revision,
    filePath: file,
  );
}

Future&amp;lt;void&amp;gt; main(List&amp;lt;String&amp;gt; args) async {
  if (args.isEmpty ||
      args.length &amp;gt; 3 ||
      (args.length &amp;gt; 1 &amp;amp;&amp;amp; !['small', 'staged', 'audio'].contains(args[1]))) {
    stderr.writeln(
      'Usage: dart run bin/voice.dart question.wav '
      '[small|staged|audio] [voice-reference.wav]',
    );
    exitCode = 64;
    return;
  }
  final question = args[0];
  final small = args.length == 1 || args[1] == 'small';
  final directAudio = args.length &amp;gt; 1 &amp;amp;&amp;amp; args[1] == 'audio';
  final reference = args.length == 3 ? args[2] : null;
  for (final path in [question, if (reference != null) reference]) {
    if (!await File(path).exists()) {
      throw ArgumentError('Audio file not found: &lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="sh"&gt;');
    }
  }

  String? transcript;
  if (!directAudio) {
    final asr = await SpeechToTextEngine.load(
      SpeechToTextModel(
        model('asr', 'Qwen3-ASR-0.6B-Q8_0.gguf'),
        projector: model('asr', 'mmproj-Qwen3-ASR-0.6B-Q8_0.gguf'),
        adapter: const Qwen3AsrAdapter(),
      ),
      params: params,
    );
    try {
      final result = await asr.transcribeOnce(
        SpeechToTextRequest(
          audio: SpeechAudioFileInput(question),
          contextPrompt: 'Mo, Pip, Dash, Llama Village',
        ),
      );
      transcript = result.text.trim();
      if (transcript.isEmpty) throw StateError('No speech recognized');
      stdout.writeln('Dash: &lt;/span&gt;&lt;span class="nv"&gt;$transcript&lt;/span&gt;&lt;span class="sh"&gt;');
    } finally {
      await asr.dispose();
    }
  }

  final llm = await LlamaEngine.load(
    LlamaModel(
      small
          ? model('dialogue', 'Qwen3.5-0.8B-Q4_K_M.gguf')
          : model('omni', 'Qwen2.5-Omni-3B-Q4_K_M.gguf'),
      projector: directAudio
          ? model('omni', 'mmproj-Qwen2.5-Omni-3B-Q8_0.gguf')
          : null,
    ),
    params: params,
  );
  final reply = StringBuffer();
  try {
    if (directAudio &amp;amp;&amp;amp; !(await llm.capabilities).supportsAudio) {
      throw StateError('This model/runtime cannot accept audio');
    }
    await for (final chunk in llm.create(
      [
        const LlamaChatMessage.fromText(
          role: LlamaChatRole.system,
          text:
              'You are Mo, a friendly baker llama. Dash is visiting you. '
              "Known fact: Pip's red scarf is in the reeds beside the pond. "
              'Answer the visitor in one short sentence in English. '
              'No stage directions. Do not invent facts you do not know.',
        ),
        if (transcript != null)
          LlamaChatMessage.fromText(role: LlamaChatRole.user, text: transcript)
        else
          LlamaChatMessage.withContent(
            role: LlamaChatRole.user,
            content: [LlamaAudioContent(path: question)],
          ),
      ],
      enableThinking: false,
      params: const GenerationParams(maxTokens: 100, temp: 0, seed: 42),
    )) {
      reply.write(chunk.text);
    }
  } finally {
    await llm.dispose();
  }

  final text = reply.toString().trim();
  if (text.isEmpty) throw StateError('The model returned no reply');
  stdout.writeln('Mo: &lt;/span&gt;&lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="sh"&gt;');
  final tts = await TextToSpeechEngine.load(
    TextToSpeechModel(
      small
          ? model('smallTts', 'Qwen3-TTS-12Hz-0.6B-Base.Q4_K_M.gguf')
          : model('tts', 'Qwen3-TTS-12Hz-1.7B-Base-Q4_K_M.gguf'),
      projector: small
          ? model('smallTts', 'Qwen3-TTS-12Hz-0.6B-Base.mmproj-Q8_0.gguf')
          : model('tts', 'mmproj-Qwen3-TTS-12Hz-1.7B-Base-Q8_0.gguf'),
      adapter: const Qwen3TtsAdapter(),
    ),
    params: params,
  );
  try {
    final audio = await tts.synthesizeOnce(
      TextToSpeechRequest(
        text: text,
        language: 'en',
        seed: 42,
        speakerReference: reference == null
            ? null
            : SpeechAudioFileInput(reference),
      ),
    );
    await File('reply.wav').writeAsBytes(audio.toWavBytes());
    stdout.writeln('Saved reply.wav');
  } finally {
    await tts.dispose();
  }
}
&lt;/span&gt;&lt;span class="no"&gt;DART
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ask Mo a question
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;say &lt;span class="nt"&gt;-o&lt;/span&gt; question.aiff &lt;span class="s2"&gt;"Mo, have you seen Pip's red scarf?"&lt;/span&gt;
afconvert &lt;span class="nt"&gt;-f&lt;/span&gt; WAVE &lt;span class="nt"&gt;-d&lt;/span&gt; LEI16@16000 &lt;span class="nt"&gt;-c&lt;/span&gt; 1 question.aiff question.wav
dart run bin/voice.dart question.wav small
afplay reply.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a synthetic question so you can try the whole path without a microphone package or a sample download. You can replace &lt;code&gt;question.wav&lt;/code&gt; with your own short WAV. The program prints the transcript and answer, then saves &lt;code&gt;reply.wav&lt;/code&gt; in the project directory. It does not record a microphone.&lt;/p&gt;

&lt;p&gt;The main calls inside the program are:&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="c1"&gt;// ASR: a recording becomes text.&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;asr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transcribeOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;SpeechToTextRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;audio:&lt;/span&gt; &lt;span class="n"&gt;SpeechAudioFileInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;contextPrompt:&lt;/span&gt; &lt;span class="s"&gt;'Mo, Pip, Dash, Llama Village'&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="c1"&gt;// TTS: the generated answer becomes a WAV file.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;audio&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;tts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;synthesizeOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;TextToSpeechRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;language:&lt;/span&gt; &lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;seed:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'reply.wav'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeAsBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toWavBytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These excerpts sit inside the loaded engines’ &lt;code&gt;try/finally&lt;/code&gt; blocks in the full program. Between them, &lt;code&gt;llm.create&lt;/code&gt; answers using the transcript and Mo’s supplied fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try the same question as audio
&lt;/h3&gt;

&lt;p&gt;Change the mode to download and load Omni’s audio-capable model and projector, plus the original 1.7B TTS pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart run bin/voice.dart question.wav audio
afplay reply.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That route skips ASR and sends &lt;code&gt;LlamaAudioContent(path: question)&lt;/code&gt; to the dialogue model. The program checks &lt;code&gt;supportsAudio&lt;/code&gt; before asking it to answer. The files used by this route total about &lt;strong&gt;5.13 GB&lt;/strong&gt;; keeping all three modes cached takes about &lt;strong&gt;7.44 GB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the game, Mo should only receive facts he is allowed to reveal. &lt;a href="https://dev.to/gde/can-a-local-ai-model-learn-to-play-tetris-8m8"&gt;DecisionEngine&lt;/a&gt; could choose an allowed intention before dialogue generation. A friendly persona prompt does not enforce those rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it sound like Mo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frga6e2lk5ez5wz0ujqhk.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frga6e2lk5ez5wz0ujqhk.png" alt="Design a voice once and reuse its reference" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A possible production workflow: prepare a voice, save a reference, and reuse it for new dialogue. Concept illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’d like Mo to sound warm and unhurried. Dash could be brighter and quicker. A short reference clip lets several characters share one TTS model while sounding different. When the player speaks as Dash, they already provide his voice.&lt;/p&gt;

&lt;p&gt;The full program accepts an optional third argument. Use &lt;code&gt;small&lt;/code&gt; for the 0.8B/0.6B pair, or &lt;code&gt;staged&lt;/code&gt; for the original 3B/1.7B pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart run bin/voice.dart question.wav small mo-reference.wav
afplay reply.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a short, clear recording you have permission to use. For my test I made a synthetic reference with the macOS Daniel voice. Inside &lt;code&gt;TextToSpeechRequest&lt;/code&gt;, the program passes it as &lt;code&gt;speakerReference: SpeechAudioFileInput(reference)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/QwenLM/Qwen3-TTS#voice-design" rel="noopener noreferrer"&gt;Qwen’s voice-design workflow&lt;/a&gt; also lets you create a voice during development, then use the resulting clip with a Base model. Players would download the synthesis model and reference, without needing the voice-design model. I have not tested that workflow here.&lt;/p&gt;

&lt;p&gt;A reference gives Mo a voice, but the tested API has no instruction such as “say this nervously.” That is not just a missing Dart parameter. Qwen’s 1.7B CustomVoice and VoiceDesign models support instruction control; the Base model used here does not offer that same mode. Exposing it would need a compatible model, native runtime support, and a Dart API. Changing the dialogue prompt only changes the words.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller models, smaller downloads. Faster conversations?
&lt;/h2&gt;

&lt;p&gt;I tested the smaller pair on the same M4 Max (64 GB), with llamadart 0.11.0. Both routes kept Qwen3-ASR 0.6B and used the same synthetic question and Daniel voice reference.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Text route&lt;/th&gt;
&lt;th&gt;Model files&lt;/th&gt;
&lt;th&gt;Complete turn, median&lt;/th&gt;
&lt;th&gt;Peak process RSS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Omni 3B + TTS 1.7B&lt;/td&gt;
&lt;td&gt;4.61 GB&lt;/td&gt;
&lt;td&gt;4.27 s&lt;/td&gt;
&lt;td&gt;3.86 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5 0.8B + TTS 0.6B&lt;/td&gt;
&lt;td&gt;2.31 GB&lt;/td&gt;
&lt;td&gt;5.83 s&lt;/td&gt;
&lt;td&gt;2.51 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Half the download, about 35% less peak RSS, but a slower turn in this pilot.&lt;/strong&gt; The smaller dialogue model gave a longer reply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;3B:&lt;/strong&gt; “Yes, Pip's red scarf is in the reeds beside the pond.”&lt;br&gt;&lt;br&gt;
&lt;strong&gt;0.8B:&lt;/strong&gt; “Yes, I saw it! It was tucked inside the reeds by the pond.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The 0.8B model also ignored the one-sentence instruction. More words mean more speech to generate. These are complete application turns with different replies, so I also tested the components separately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Loaded component&lt;/th&gt;
&lt;th&gt;Generation time, median&lt;/th&gt;
&lt;th&gt;Peak process RSS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Omni 3B dialogue&lt;/td&gt;
&lt;td&gt;171 ms&lt;/td&gt;
&lt;td&gt;2.57 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5 0.8B dialogue&lt;/td&gt;
&lt;td&gt;96 ms&lt;/td&gt;
&lt;td&gt;1.08 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-TTS 1.7B&lt;/td&gt;
&lt;td&gt;1.78 s&lt;/td&gt;
&lt;td&gt;3.85 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-TTS 0.6B&lt;/td&gt;
&lt;td&gt;1.78 s&lt;/td&gt;
&lt;td&gt;2.48 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For TTS, both models spoke exactly “Yes, Pip's red scarf is in the reeds beside the pond.” The 1.7B clips lasted 4.16–4.56 seconds; the 0.6B clips lasted 4.80–6.48 seconds. The smaller model’s synthesis times ranged from 1.78 to 2.53 seconds. Smaller did not mean a consistently shorter wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did Mo say the right thing?
&lt;/h3&gt;

&lt;p&gt;Both dialogue models supplied the location and colour correctly when asked. But when I asked &lt;strong&gt;“Who put the scarf in the pond?”&lt;/strong&gt;, both blamed Pip. That fact was never supplied, even with “Do not invent facts you do not know” in the prompt. The smaller model’s “I saw it” also adds an unsupported claim about how Mo knows.&lt;/p&gt;

&lt;p&gt;All six measured TTS clips transcribed back to the intended sentence with Qwen3-ASR. That is an intelligibility check, not a listening score for naturalness, emotion, or similarity to Daniel. I would still listen to more lines before choosing a character voice.&lt;/p&gt;

&lt;p&gt;I would use the smaller pair for this prototype’s storage budget. I would keep the game’s knowledge rules outside the prompt, and measure real conversations before claiming a latency win.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Test conditions and model files
  &lt;p&gt;This is a small development-machine pilot, not a device benchmark. Other development workloads were active; the village renderer was not. The complete-turn test alternated the two setups, discarded the first turn for each, then measured three fresh-process turns per setup. Baseline turns ranged from 3.76 to 4.41 seconds; smaller-pair turns ranged from 5.82 to 5.91 seconds. Timing includes model loading and disposal, but excludes process startup, downloads, recording, and playback. Peak RSS was sampled every 100 ms and is the largest observed peak across the three runs. It is process memory, not total GPU or device memory.&lt;/p&gt;

&lt;p&gt;Component tests loaded each model in its own process and discarded one warm-up. Dialogue used three questions: the scarf’s location, who put it there, and its location plus colour. TTS repeated the same sentence three times with the same reference. Component times exclude loading and disposal; memory uses the process high-water RSS. Both dialogue models used temperature 0, seed 42, a 100-token limit, thinking disabled, a 4096-token context, and four threads. TTS used seed 42; its waveforms still varied between calls.&lt;/p&gt;

&lt;p&gt;The dialogue weights are Q4_K_M; audio projectors are Q8_0. The new files are &lt;a href="https://huggingface.co/unsloth/Qwen3.5-0.8B-GGUF/tree/6ab461498e2023f6e3c1baea90a8f0fe38ab64d0" rel="noopener noreferrer"&gt;Unsloth’s Qwen3.5 0.8B conversion&lt;/a&gt; and &lt;a href="https://huggingface.co/mradermacher/Qwen3-TTS-12Hz-0.6B-Base-GGUF/tree/0762b31e9cd99274d49dca4e895bd81863134f08" rel="noopener noreferrer"&gt;mradermacher’s Qwen3-TTS 0.6B Base conversion&lt;/a&gt;. The exact revisions and matching projector filenames are pinned in the program above. A GGUF file made for a different TTS runtime is not automatically interchangeable.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the player read while Mo gets ready
&lt;/h2&gt;

&lt;p&gt;This TTS API returns the complete audio before playback. Its progress events are not playable audio chunks. I’d show Mo’s reply as text immediately, then play it when synthesis finishes.&lt;/p&gt;

&lt;p&gt;For a first game version, I’d use push-to-talk, stop recording while Mo speaks, and discard an old reply if the player starts another turn. Text should remain available when voice is switched off or a model cannot load. I still need to measure speech alongside rendering before adding this to the game.&lt;/p&gt;

&lt;p&gt;For model pairs and platform requirements, see the &lt;a href="https://llamadart.leehack.com/docs/guides/speech-to-text" rel="noopener noreferrer"&gt;speech recognition guide&lt;/a&gt; and &lt;a href="https://llamadart.leehack.com/docs/guides/text-to-speech" rel="noopener noreferrer"&gt;speech synthesis guide&lt;/a&gt;. Partial live captions use a different recognition path from this file-based example.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dart</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Local embeddings with llamadart: how llamas share secrets</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Mon, 12 Oct 2026 18:18:49 +0000</pubDate>
      <link>https://dev.to/gde/local-embeddings-with-llamadart-how-llamas-share-secrets-46o</link>
      <guid>https://dev.to/gde/local-embeddings-with-llamadart-how-llamas-share-secrets-46o</guid>
      <description>&lt;p&gt;&lt;em&gt;Beyond chat with llamadart · Part 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo1pmkf0fajfapzc2ixu.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo1pmkf0fajfapzc2ixu.jpg" alt="Llama Village at golden hour" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Llama Village, where five llamas share a world without sharing everything they know.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pip’s red scarf is missing. Mo knows where it is. He also knows how it got there—and would rather keep that part to himself.&lt;/p&gt;

&lt;p&gt;That gives the village a small mystery. It gives the game a harder question: when two llamas talk, what should the listener actually learn?&lt;/p&gt;

&lt;p&gt;I introduced the game in &lt;a href="https://dev.to/gde/llama-village-a-virtual-world-powered-by-local-ai-with-llamadart-3lmi"&gt;Llama Village: a virtual world powered by local AI with llamadart&lt;/a&gt;. Here, we will follow one part of its machinery: local embeddings help compare what a llama said with facts that might have been shared.&lt;/p&gt;

&lt;p&gt;The destination is a change you can inspect: a llama acquires information, the game records who told them, and they may still doubt it. Dialogue, embeddings, and game rules each have a different job.&lt;/p&gt;

&lt;h2&gt;
  
  
  A secret starts with one llama
&lt;/h2&gt;

&lt;p&gt;The game stores the scarf’s location and Mo’s accident as separate facts. At the start, Mo knows both. Knowing where the scarf is does not automatically mean knowing who knocked it into the pond.&lt;/p&gt;

&lt;p&gt;Before checking a conversation, the game narrows the possibilities: facts the speaker knows and the listener does not. It compares each fact with that speaker’s lines, not every line from both characters.&lt;/p&gt;

&lt;p&gt;An embedding model has learned text representations from training data. It maps each line and fact to a numeric vector; cosine similarity compares those vectors. Similar wording and related meaning can bring vectors closer, but the score is not a yes/no test for identical meaning, truth, or whether a statement implies a fact. The game uses each fact’s highest similarity across the speaker’s lines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frrmjre4fm8u2t2pn3qnh.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frrmjre4fm8u2t2pn3qnh.png" alt="Mo speaks, the game checks, June remembers" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI-generated overview: compare meaning, check evidence, then record who said it. A conceptual illustration, not a captured game trace.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The implementation uses EmbeddingGemma through llamadart and caches vectors by text. It applies &lt;code&gt;title: none | text:&lt;/code&gt; to both lines and facts. This walkthrough reproduces that game choice. &lt;a href="https://ai.google.dev/gemma/docs/embeddinggemma/model_card#prompt-instructions" rel="noopener noreferrer"&gt;Google’s model card&lt;/a&gt; recommends task-specific prefixes for new applications, including a sentence-similarity query prompt; changing the format requires reevaluating the scores and thresholds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The words do not have to match
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqdl3g6jzhl5dd1jtxod.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqdl3g6jzhl5dd1jtxod.jpg" alt="June and Bramble in conversation" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;June and Bramble talking about poems in a recorded playthrough. This shows the dialogue system, not the scarf exchange illustrated below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For a small walkthrough, compare the stored fact “Pip’s red scarf is lying in the pond reeds” with the illustrative line “I saw Pip’s scarf tangled in the reeds by the pond.” They describe related information using different words. The line is written for this explanation, not quoted from the recording.&lt;/p&gt;

&lt;p&gt;The input is text. The embedding output is a numeric vector. The application then calculates a similarity score; the model does not return “June learned the scarf’s location.” That decision comes later.&lt;/p&gt;

&lt;p&gt;Keep the same embedding model and compatible settings for both texts. Equal vector lengths alone do not guarantee compatible representations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the text through llamadart
&lt;/h2&gt;

&lt;p&gt;With a suitable embedding engine loaded, the core calls look like this. The full loading and comparison example is at the end.&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;facts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"Pip's red scarf is lying in the pond reeds."&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;factVectors&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;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;embedBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&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="s"&gt;'title: none | text: &lt;/span&gt;&lt;span class="si"&gt;$text&lt;/span&gt;&lt;span class="s"&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;toList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nl"&gt;normalize:&lt;/span&gt; &lt;span class="kc"&gt;true&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;lineVector&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;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"title: none | text: I saw Pip's scarf tangled "&lt;/span&gt;
  &lt;span class="s"&gt;"in the reeds by the pond."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;normalize:&lt;/span&gt; &lt;span class="kc"&gt;true&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 game’s configured embedding artifact is &lt;code&gt;embeddinggemma-300M-Q8_0.gguf&lt;/code&gt;. For another model, follow its input-format requirements. The &lt;a href="https://llamadart.leehack.com/docs/guides/embeddings" rel="noopener noreferrer"&gt;llamadart embedding guide&lt;/a&gt; covers loading, backend support, and input limits.&lt;/p&gt;

&lt;p&gt;For normalized vectors, a dot product gives cosine similarity. In the game, keyword evidence adds a ranking bonus of 0.3, and the best six candidates go to the outcome model. These are game-specific choices, not standard embedding thresholds.&lt;/p&gt;

&lt;p&gt;I ran the companion example using published &lt;code&gt;llamadart 0.11.0&lt;/code&gt; and the local &lt;code&gt;embeddinggemma-300M-Q8_0.gguf&lt;/code&gt; artifact on macOS arm64. It returned 768-dimensional vectors. Here are selected cosine similarities from that run; these are illustrative inputs run through the real model, not dialogue captured during gameplay.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input line&lt;/th&gt;
&lt;th&gt;Compared with&lt;/th&gt;
&lt;th&gt;Cosine similarity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;I saw Pip’s scarf tangled in the reeds by the pond.&lt;/td&gt;
&lt;td&gt;Scarf location&lt;/td&gt;
&lt;td&gt;0.8449&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I did not knock the scarf into the pond.&lt;/td&gt;
&lt;td&gt;Mo’s accident&lt;/td&gt;
&lt;td&gt;0.6085&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The sky looks beautiful today.&lt;/td&gt;
&lt;td&gt;Scarf location&lt;/td&gt;
&lt;td&gt;0.3137&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The denial is the revealing case: it remains similar to the accident fact despite saying the opposite. These scores belong to this artifact and formatting; they are not universal cutoffs or probabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who decides whether the meaning matches
&lt;/h2&gt;

&lt;p&gt;Embeddings do not first detect a topic and then prove matching meaning. They produce one representation and a similarity score. To ask “did this speaker actually reveal that fact?”, Llama Village sends the conversation and shortlisted facts to a separate generative outcome model. That model is prompted to return the IDs whose content was clearly said aloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybzflor5eclak2keb8qt.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybzflor5eclak2keb8qt.png" alt="Similar subject matter can hide opposite meanings" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Illustrative alternatives, not a recorded exchange: mentioning the same scarf and pond does not make a denial a confession. AI-generated illustration; no similarity scores are implied.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the illustrative denial “I did not knock the scarf into the pond,” the intended interpretation is that the accident was not confessed. The outcome model must interpret the wording and context to reach that conclusion; it can be wrong. Its JSON constrains the shape and allowed IDs of the response, not whether the interpretation is correct.&lt;/p&gt;

&lt;p&gt;The code then combines the model’s proposed revelations with keywords and similarity. The location secret requires “scarf” and a location term. The accident secret requires “scarf” and a responsibility term such as “knock.” Crucially, “I did not knock the scarf into the pond” still matches those substring groups. They do not understand the negation.&lt;/p&gt;

&lt;p&gt;There is a real weakness in the current game rule. For a secret, matching keywords plus similarity ≥ 0.5 can accept a transfer even when the outcome model does &lt;strong&gt;not&lt;/strong&gt; claim it. Feeding the measured denial score of 0.6085 into the game’s actual &lt;code&gt;keywordHit&lt;/code&gt; and &lt;code&gt;acceptTransfer&lt;/code&gt; functions returned &lt;code&gt;accepted=true&lt;/code&gt; with &lt;code&gt;claimed=false&lt;/code&gt;. This is a rule-level reproduction, not a complete gameplay run: the fact must still be eligible and reach the shortlist.&lt;/p&gt;

&lt;p&gt;The current rules therefore do not guarantee matching meaning. A stricter design would remove that similarity-only fallback for secrets and require an explicit revelation judgment with supporting dialogue.&lt;/p&gt;

&lt;p&gt;One possible next step is llamadart’s &lt;code&gt;DecisionEngine&lt;/code&gt;, introduced with Laya in &lt;a href="https://dev.to/gde/can-a-local-ai-model-learn-to-play-tetris-8m8"&gt;Can a local AI model learn to play Tetris?&lt;/a&gt;. Here, the choice would be about Mo’s words: given the dialogue and a candidate fact, does he &lt;strong&gt;reveal it, deny it, or leave it unclear&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embeddings shortlist → DecisionEngine judges → game applies the rules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only a “revealed” decision would let the fact proceed through the game’s knowledge-transfer checks; similarity alone would no longer accept it. This is a proposed extension, not the current implementation. A structured answer can still be wrong: we would need to evaluate Laya on denial, speculation, quotation, and ambiguous dialogue—and potentially tune a dedicated head—before claiming it improves on the current outcome model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current secret transfer check&lt;/th&gt;
&lt;th&gt;Required evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Keywords&lt;/td&gt;
&lt;td&gt;Every keyword group matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additional support&lt;/td&gt;
&lt;td&gt;Outcome model claims it &lt;strong&gt;or&lt;/strong&gt; similarity ≥ 0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known weakness&lt;/td&gt;
&lt;td&gt;The similarity branch can bypass an unclaimed revelation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  June hears the story and still doubts it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ao7zxevh53ckt0pnytn.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ao7zxevh53ckt0pnytn.jpg" alt="June knows the story but doubts it" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at June’s inspector: Mo’s scarf accident is marked “heard from Mo; maybe untrue.” The screenshot shows a stored outcome, not a captured embedding score or proof of the exact dialogue that produced it.&lt;/p&gt;

&lt;p&gt;After accepting a transfer, the game records the teller and separates having heard information from believing it. Belief depends on the teller’s belief and the listener’s trust rules. That distinction lets a conversation change the village without making every listener accept every story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give your app the same separation
&lt;/h2&gt;

&lt;p&gt;The reusable pattern is: narrow candidates with application state, use embeddings to find plausible matches, and give consequential state changes a separately evaluated decision. Llama Village demonstrates the pattern and a failure mode: a similarity fallback can undo the distinction between finding related text and accepting a revelation.&lt;/p&gt;

&lt;p&gt;Keep sources and provenance alongside vectors. Rebuild cached vectors when the model or its input formatting changes. Choose an embedding model that fits the runtime, and check backend support after loading.&lt;/p&gt;

&lt;p&gt;Pip’s missing scarf makes the technical question visible: did someone say where it was, admit how it got there, deny it, or just mention the pond? Test those separately. A convincing conversation and a correct state update are two different things to evaluate.&lt;/p&gt;

&lt;p&gt;For the rest of the world, including Dash, the Berry Festival, and the storybook ending, see &lt;a href="https://dev.to/gde/llama-village-a-virtual-world-powered-by-local-ai-with-llamadart-3lmi"&gt;the Llama Village article&lt;/a&gt;. Next in this series: local speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete embedding comparison example
&lt;/h2&gt;

&lt;p&gt;This example was analyzed and executed with Dart 3.13.5 and published &lt;code&gt;llamadart 0.11.0&lt;/code&gt; on macOS arm64. Create a Dart console project, pin &lt;code&gt;llamadart: 0.11.0&lt;/code&gt; in its &lt;code&gt;pubspec.yaml&lt;/code&gt;, run &lt;code&gt;dart pub get&lt;/code&gt;, and save the code as &lt;code&gt;bin/village_embeddings.dart&lt;/code&gt;. Use a local EmbeddingGemma Q8_0 GGUF and run the command below. The two prefixes intentionally reproduce the game’s format. This compares five fixed facts; it does not implement character eligibility, the outcome model, or knowledge updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart run bin/village_embeddings.dart &lt;span class="se"&gt;\&lt;/span&gt;
  /absolute/path/to/embeddinggemma-300M-Q8_0.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'title: none | text: '&lt;/span&gt; &lt;span class="s1"&gt;'title: none | text: '&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:llamadart/llamadart.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="kt"&gt;List&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'Usage: dart run village_embeddings.dart model.gguf '&lt;/span&gt;
      &lt;span class="s"&gt;'"query prefix" "document prefix" (use "" for no prefix)'&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;const&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'Pip&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s red scarf is lying in the pond reeds.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Mo knocked Pip&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s red scarf into the pond reeds by accident and never told her.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Bramble writes anonymous love poems to June.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'Pip&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s red scarf has gone missing from her hut.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'June made up the rumour that Mo&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s bread made Bramble sick.'&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;engine&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;LlamaEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LlamaModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supportsEmbeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'The active backend does not support embeddings.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Index these once; reuse them for subsequent searches.&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;vectors&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;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;embedBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&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="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${args[2]}$text&lt;/span&gt;&lt;span class="s"&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;toList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;normalize:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&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;query&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s"&gt;'I saw Pip&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s scarf tangled in the reeds by the pond.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'I did not knock the scarf into the pond.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'The sky looks beautiful today.'&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;vector&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;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${args[1]}$query&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;normalize:&lt;/span&gt; &lt;span class="kc"&gt;true&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;ranked&lt;/span&gt; &lt;span class="o"&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;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&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;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nl"&gt;score:&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vectors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;score&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="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;$query&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="si"&gt;${vector.length}&lt;/span&gt;&lt;span class="s"&gt; dimensions'&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;hit&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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;${hit.score.toStringAsFixed(4)}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;${hit.text}&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;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Vector dimensions differ.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&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;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&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;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;Implementation references: &lt;a href="https://github.com/leehack/llama-village/blob/93d1a697e2e08b377b872582316a466e08f2aab5/lib/sim/dialogue.dart#L185" rel="noopener noreferrer"&gt;candidate selection and acceptance&lt;/a&gt; and &lt;a href="https://github.com/leehack/llama-village/blob/93d1a697e2e08b377b872582316a466e08f2aab5/lib/sim/model.dart#L293" rel="noopener noreferrer"&gt;embedding formatting&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>ai</category>
      <category>flutter</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DecisionEngine: Teaching local AI to play Tetris</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:23:42 +0000</pubDate>
      <link>https://dev.to/gde/can-a-local-ai-model-learn-to-play-tetris-8m8</link>
      <guid>https://dev.to/gde/can-a-local-ai-model-learn-to-play-tetris-8m8</guid>
      <description>&lt;p&gt;&lt;em&gt;Beyond chat with llamadart · Part 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A piece is falling. There are a few places it could land. Somewhere in the background, a local AI model is choosing one.&lt;/p&gt;

&lt;p&gt;If it takes too long, the game carries on without it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdl41ciyp86dxvwefezw.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdl41ciyp86dxvwefezw.jpg" alt="Actual Laya Tetris browser run with the tuned choice player, 51 lines cleared and its candidate probabilities visible" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The tuned player in an actual browser run: 51 lines cleared using WebGPU, Q8_0, and mixed candidates. This illustration is separate from the recorded benchmarks below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leehack-flutter-laya-tetris.static.hf.space" rel="noopener noreferrer"&gt;Try the browser demo&lt;/a&gt; · &lt;a href="https://github.com/leehack/llamadart/tree/main/example/laya_tetris" rel="noopener noreferrer"&gt;Open the example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This opens Beyond chat with llamadart, a five-part series about cross-platform local AI for Dart and Flutter. We’ll explore decisions, embeddings, speech, images, and an application that connects them.&lt;/p&gt;

&lt;p&gt;Tetris gives the experiment a visible consequence: the model picks a landing, and the board changes.&lt;/p&gt;

&lt;p&gt;The game already has a useful heuristic. What does adding a decision model buy us? Tetris lets us compare different questions, training, and the cost of waiting for an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the model a smaller job
&lt;/h2&gt;

&lt;p&gt;The planner builds the options without AI. From the active piece’s current pose, it tries rotations, horizontal shifts, and a hard drop, rejecting blocked paths.&lt;/p&gt;

&lt;p&gt;Each candidate is a &lt;code&gt;Move&lt;/code&gt;: a reachable landing, the key sequence that gets there, and the resulting board measurements. Duplicate landings keep the shortest discovered sequence.&lt;/p&gt;

&lt;p&gt;The game simulates each landing to count cleared lines, holes, stack height, and bumpiness. &lt;code&gt;describeCompact()&lt;/code&gt; turns those consequences into the text sent to Laya.&lt;/p&gt;

&lt;p&gt;The model chooses among descriptions of these planned moves. It does not generate the keys.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq4dmbbtr0p5lwb8urw5.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffq4dmbbtr0p5lwb8urw5.png" alt="Schematic Tetris board with three candidate placements and an illustrative Laya choice of B" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Three illustrative landings, with B shown as an example answer. Laya receives descriptions, not board images. Game code validates and executes the move.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gravity keeps running. A target can become unreachable before the answer arrives, and the game records those failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Jev comes in
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.typesafe.ai/introduction" rel="noopener noreferrer"&gt;TypeSafe’s Jev&lt;/a&gt; gives this approach a useful vocabulary. TypeSafe calls it System One: provide a state and typed questions, then use the structured answers directly in code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/convaiinnovations/laya" rel="noopener noreferrer"&gt;Laya&lt;/a&gt; is a separate open-weight model with a Jev-compatible interface. Llamadart follows Jev’s vocabulary and Laya’s request format; this experiment runs Laya locally.&lt;/p&gt;

&lt;p&gt;The results here measure Laya and its Tetris-tuned head, not Jev.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make your first decision with llamadart
&lt;/h2&gt;

&lt;p&gt;A ModernBERT encoder runs through llama.cpp, and a decision head produces the answer. Each question takes an encoder pass without generating prose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89gsg5g9ugaylf6ju3ez.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89gsg5g9ugaylf6ju3ez.png" alt="Game engine and model round trip" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One question through the local pipeline. DecisionEngine prepares the tokens and decodes the model’s scores. The game continues running while it awaits the result.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ModernBERT reads the question, options, and state together. The head scores the positions marking each option; the decoder turns those scores into probabilities. Tuning changes the head while the encoder stays frozen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load a model and ask for a move
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;llamadart&lt;/code&gt; to a Dart project with &lt;code&gt;dart pub add llamadart&lt;/code&gt;, save the following as &lt;code&gt;bin/decision_walkthrough.dart&lt;/code&gt;, and run &lt;code&gt;dart run bin/decision_walkthrough.dart&lt;/code&gt; on a supported native target. The documented decision path is validated on macOS; consult the &lt;a href="https://llamadart.leehack.com/docs/guides/decision-models#current-support-matrix" rel="noopener noreferrer"&gt;support matrix&lt;/a&gt; for other targets. The first run downloads a roughly 421 MB encoder and 106 MB head. This standalone example prints one decision; it does not launch the Tetris game.&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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:llamadart/llamadart.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="kt"&gt;List&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;useTunedHead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'--tuned'&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;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'fr0stbit3/laya-gguf'&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;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'ce2afdc0a8766af56a29a22dcf4a781e1f5c7d3c'&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;decisions&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;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DecisionModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;encoder:&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;huggingFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;repoId:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;revision:&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;filePath:&lt;/span&gt; &lt;span class="s"&gt;'laya-Q8_0.gguf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;head:&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;huggingFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;repoId:&lt;/span&gt; &lt;span class="n"&gt;useTunedHead&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;'leehack/laya-tetris-head'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;revision:&lt;/span&gt; &lt;span class="n"&gt;useTunedHead&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;'465546a595ee2e8e3b212b8cb16829205d5dfab6'&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;filePath:&lt;/span&gt; &lt;span class="n"&gt;useTunedHead&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;'laya-head-tetris.safetensors'&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'laya-head.safetensors'&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;params:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DecisionModelParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;device:&lt;/span&gt; &lt;span class="n"&gt;ComputeDevice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;threads:&lt;/span&gt; &lt;span class="mi"&gt;4&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Illustrative descriptions, not a captured game turn.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&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="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 1 to 2: clears zero lines, no new holes, '&lt;/span&gt;
          &lt;span class="s"&gt;'stack 5 rows, bumpiness 8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'B'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 5 to 6: clears one line, no new holes, '&lt;/span&gt;
          &lt;span class="s"&gt;'stack 3 rows, bumpiness 4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'C'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 9 to 10: clears zero lines, adds one hole, '&lt;/span&gt;
          &lt;span class="s"&gt;'stack 6 rows, bumpiness 10'&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;decisions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;state:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'game'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'tetris'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'piece'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'O'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;questions:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'move'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DecisionQuestion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;'Which placement of the falling Tetris piece is best?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;criteria:&lt;/span&gt; &lt;span class="n"&gt;candidates&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;ChoiceAnswer&lt;/span&gt; &lt;span class="n"&gt;answer&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;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'move'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;selectedDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;'Choice: &lt;/span&gt;&lt;span class="si"&gt;${answer.choice}&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;'Probabilities: &lt;/span&gt;&lt;span class="si"&gt;${answer.probabilities}&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;'Candidate: &lt;/span&gt;&lt;span class="si"&gt;$selectedDescription&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;decisions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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;&lt;code&gt;DecisionModel&lt;/code&gt; identifies the encoder and head. &lt;code&gt;load&lt;/code&gt; prepares their files and owns both resources. &lt;code&gt;systemOne&lt;/code&gt; answers the named questions about one state; &lt;code&gt;criteria&lt;/code&gt; maps each candidate label to its description.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read the typed answer
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;result.choices['move']&lt;/code&gt; returns a &lt;code&gt;ChoiceAnswer&lt;/code&gt;. Its &lt;code&gt;choice&lt;/code&gt; is the selected label, and &lt;code&gt;probabilities&lt;/code&gt; contains every option’s probability. Here is an illustrative rendering of those fields, not captured output; your run may choose a different option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"choice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"probabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses &lt;code&gt;candidates[answer.choice]&lt;/code&gt; to recover the selected description. In the game, that same lookup leads back to a &lt;code&gt;Move&lt;/code&gt; with its landing and planned keys. The tuned player chooses from the probabilities itself so it can break ties randomly; &lt;code&gt;ChoiceAnswer.choice&lt;/code&gt; uses the first highest-probability option. A probability of 0.75 is not a 75% guarantee of success.&lt;/p&gt;

&lt;h3&gt;
  
  
  From B to button presses
&lt;/h3&gt;

&lt;p&gt;The mapping stays in game memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;B → original candidate → Move {
  landing,
  planned keys,
  board measurements
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before pressing anything, the game checks that it is still controlling the same piece. Gravity has continued during inference, so it runs the planner again and finds a route to B’s landing from the current pose. It queues that route and presses one action at the configured interval.&lt;/p&gt;

&lt;p&gt;If the piece has already locked, the answer is discarded. If the landing is now blocked, the game records the failure and hard-drops. The &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/example/laya_tetris/lib/main.dart#L361" rel="noopener noreferrer"&gt;execution code&lt;/a&gt; handles those cases.&lt;/p&gt;

&lt;p&gt;The API supports three kinds of question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Choice&lt;/td&gt;
&lt;td&gt;A selected option and option probabilities&lt;/td&gt;
&lt;td&gt;Which landing should we use?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Score&lt;/td&gt;
&lt;td&gt;An expected value across ordered levels&lt;/td&gt;
&lt;td&gt;How urgent is this request?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noul&lt;/td&gt;
&lt;td&gt;Probability of a yes/no statement&lt;/td&gt;
&lt;td&gt;Does this move clear a line?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;“Noul” keeps the existing Jev and Laya vocabulary. A score can fall between levels, and a probability is not a boolean until the application applies a rule.&lt;/p&gt;

&lt;p&gt;A chat model could also choose a move. There is no matched chat-model benchmark here, so this experiment cannot establish an advantage over LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two settings to keep separate
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it changes&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Player&lt;/td&gt;
&lt;td&gt;How a move is selected&lt;/td&gt;
&lt;td&gt;Human, heuristic, random, Laya judge, checklist, base choice, tuned choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Candidates&lt;/td&gt;
&lt;td&gt;Which planned moves are considered&lt;/td&gt;
&lt;td&gt;3 best + 3 random, 6 best, 6 random, all generated legal moves&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The heuristic player considers all generated legal moves. Base choice accepts at most six, so “all” falls back to the mixed shortlist for that player. Tuned choice handles larger sets through knockout rounds. The comparisons below use the mixed shortlist unless stated otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  First result: one line
&lt;/h2&gt;

&lt;p&gt;Give the base head six candidates and ask it to choose. In the short recorded benchmark, that player clears an average of &lt;strong&gt;one line&lt;/strong&gt;. Random choice also clears one.&lt;/p&gt;

&lt;p&gt;Asking “is this a good move?” about each candidate brings the mean to &lt;strong&gt;18 lines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A narrower checklist reaches &lt;strong&gt;35 lines&lt;/strong&gt;: ask whether a move clears a line and whether it creates holes, then subtract the second probability from the first.&lt;/p&gt;

&lt;p&gt;The game already knows whether a move clears a line or creates a hole. The checklist tests how well Laya interprets those facts from text; an application could read the computed values directly. Six candidates can mean twelve model questions.&lt;/p&gt;

&lt;p&gt;That improvement costs work. In this implementation, batching the questions into a call does not turn them into one encoder pass.&lt;/p&gt;

&lt;p&gt;All three receive a shuffled shortlist: the heuristic’s three best moves plus three random remaining moves. Could training make the single-choice approach useful too?&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the heuristic be the teacher
&lt;/h2&gt;

&lt;p&gt;Only the decision head is tuned; the encoder stays frozen. Run &lt;code&gt;dart run bin/decision_walkthrough.dart --tuned&lt;/code&gt; to try the pinned Tetris head with the same encoder. One sample decision is a smoke check; the gameplay comparison comes later.&lt;/p&gt;

&lt;p&gt;The dataset has 16,000 training questions and 2,000 validation questions from other games. Labels come from the heuristic; tied best moves share the target probability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07vsncakt8a2wfxt4pp8.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07vsncakt8a2wfxt4pp8.png" alt="Agreement with heuristic-best options on 2000 validation questions: random 27.5 percent, base 30.5 percent, published tuned head 75.7 percent" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the held-out questions, the base head scored 30.5% accuracy. A random pick scored 27.5%; the task can have multiple equally best options, so this is not simply one chance in six.&lt;/p&gt;

&lt;p&gt;The published head reaches &lt;strong&gt;75.7%&lt;/strong&gt;. Eight documented 12-epoch runs ranged from 75.5% to 78.5%. The &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/example/laya_tetris/training/README.md" rel="noopener noreferrer"&gt;training recipe&lt;/a&gt; includes the full results.&lt;/p&gt;

&lt;p&gt;This measures imitation of the heuristic, not optimal play. MPS training was not bit-for-bit repeatable.&lt;/p&gt;

&lt;p&gt;A useful control: the base head still played roughly randomly when given the tuned question format. New wording alone did not explain the improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Train a head of your own
&lt;/h3&gt;

&lt;p&gt;Training happens in the Python notebook; &lt;code&gt;DecisionEngine&lt;/code&gt; loads and runs the resulting head. From the prepared &lt;code&gt;example/laya_tetris&lt;/code&gt; project, generate the seeded dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart run bin/make_dataset.dart dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/example/laya_tetris/training/laya_head_tuning.ipynb" rel="noopener noreferrer"&gt;training/laya_head_tuning.ipynb&lt;/a&gt; using the &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/example/laya_tetris/training/README.md#prerequisites" rel="noopener noreferrer"&gt;documented Python environment&lt;/a&gt;. The notebook keeps the encoder frozen, trains the head, evaluates held-out questions, and writes &lt;code&gt;laya-head-tetris.safetensors&lt;/code&gt;. Its embedded configuration lets llamadart load it without a separate config file.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;compare_heads.dart&lt;/code&gt;, replace the &lt;code&gt;tunedHead:&lt;/code&gt; source with &lt;code&gt;ModelSource.path('training/laya-head-tetris.safetensors')&lt;/code&gt;, adjusting the path to your exported file. For the first walkthrough, use the same source as &lt;code&gt;DecisionModel.head&lt;/code&gt;. Keep the matching encoder; the Dart API runs the head after Python training.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compare two heads on one encoder
&lt;/h3&gt;

&lt;p&gt;For an app that switches between base and tuned players, load the encoder once and attach both heads. The essential calls are below. The complete runnable comparison includes the pinned model sources, request, &lt;code&gt;main()&lt;/code&gt; invocation, and cleanup, including partial loading failures. Save it as &lt;code&gt;bin/compare_heads.dart&lt;/code&gt; in the same Dart project and run &lt;code&gt;dart run bin/compare_heads.dart&lt;/code&gt;.&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="c1"&gt;// Excerpt from compare_heads.dart; setup and cleanup are in the file.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&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;LlamaEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;LlamaModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;params:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DecisionModelParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encoderModelParams&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="kd"&gt;base&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;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;head:&lt;/span&gt; &lt;span class="n"&gt;baseHead&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;tuned&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;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;head:&lt;/span&gt; &lt;span class="n"&gt;tunedHead&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;baseResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemOneBatch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;request&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;tunedResults&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;tuned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemOneBatch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;request&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="n"&gt;baseResults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;single&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'move'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;probabilities&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="n"&gt;tunedResults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;single&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'move'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;probabilities&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DecisionRequest&lt;/code&gt; packages a state and its questions for &lt;code&gt;systemOneBatch&lt;/code&gt;. Results retain request order; &lt;code&gt;.single&lt;/code&gt; reads this one-request comparison. For a larger comparison, supply several requests to each head. Each question still needs its own encoder pass. Attached heads borrow the encoder, so dispose the heads first, then the encoder. In a game, keep them loaded across turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the board
&lt;/h2&gt;

&lt;p&gt;The tuned choice player clears &lt;strong&gt;48 lines&lt;/strong&gt; and reaches the 150-piece cap in every game. Then the plain heuristic clears &lt;strong&gt;56&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flccy5sgfsrm8excn9vu4.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flccy5sgfsrm8excn9vu4.png" alt="Historical mean lines cleared: random 1, base choice 1, base yes-no judge 18, base checklist 35, tuned choice 48 and heuristic baseline 56" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Recorded turn-based results on M4 Max, Metal, Q8_0. Model and random rows use 3 best + 3 random candidates; the heuristic (&lt;/em&gt;) considers all legal moves. These are descriptive means without per-seed intervals. Source: &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/example/laya_tetris/README.md#measured" rel="noopener noreferrer"&gt;Tetris benchmark&lt;/a&gt;.*&lt;/p&gt;

&lt;p&gt;The model learned something useful. The teacher still leads.&lt;/p&gt;

&lt;p&gt;For six candidates, the tuned player needs just one question.&lt;/p&gt;

&lt;p&gt;The heuristic considers all legal landings; the model’s shortlist already includes heuristic-selected moves. This is not a model learning Tetris unaided.&lt;/p&gt;

&lt;h2&gt;
  
  
  The board does not wait
&lt;/h2&gt;

&lt;p&gt;In a live game, decision quality includes arriving on time. A stronger answer that arrives after the piece locks cannot help that move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.typesafe.ai/introduction/quickstart" rel="noopener noreferrer"&gt;Jev’s documented hosted API&lt;/a&gt; involves a network request. Running locally removes that round trip and its variability. Whether it is faster overall still depends on the device, model, and workload; this experiment does not include a matched Jev benchmark.&lt;/p&gt;

&lt;p&gt;That gives task-specific training a practical purpose. A small local model does not have to be the strongest general model to be useful. It needs to make sufficiently good decisions within the application’s deadline. Here, tuning improves the single-choice player while retaining the same encoder and decision-head architecture.&lt;/p&gt;

&lt;p&gt;One six-option, 175-token question took about &lt;strong&gt;20 milliseconds&lt;/strong&gt; on Metal in the recorded M4 Max measurements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2aaxfpgskjjv9dtmqn6.png" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2aaxfpgskjjv9dtmqn6.png" alt="Historical six-option question latency ranges on M4 Max: Metal 19.6 to 19.7 ms, CPU eight threads 83.5 to 85.2, six threads 108.4 to 110.8, four threads 157.9 to 159, two threads 306.3 to 312.5" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Observed ranges across three runs per setting, using Q8_0 and a fresh engine for each setting. Model preparation is excluded; these are question timings, not full game-turn latency.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;More candidates mean more work. With all legal moves, the tuned player runs knockout rounds in groups of up to six.&lt;/p&gt;

&lt;p&gt;Across 40 documented real-time games, the tuned head averaged &lt;strong&gt;52.3 lines&lt;/strong&gt; with the mixed shortlist and &lt;strong&gt;51.5&lt;/strong&gt; with all candidates. Every answer arrived before the piece locked.&lt;/p&gt;

&lt;p&gt;These live games and the earlier capped tests have different conditions. Their scores belong to separate experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does clearing lines prove it works
&lt;/h2&gt;

&lt;p&gt;It proves the player can clear lines. The integration needs other checks.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/leehack/llamadart/blob/e7c80343e6616fd2585f1ae5853ee4b114f3ed55/doc/decision_engine.md#measured" rel="noopener noreferrer"&gt;validation record&lt;/a&gt; compares the implementation with Laya 0.3.5. A 24-question fixture showed no changed decisions across the measured configurations.&lt;/p&gt;

&lt;p&gt;A broader 187-question set caught a Q8_0 CPU yes/no probability changing from &lt;strong&gt;0.694 to 0.457&lt;/strong&gt;, crossing the 0.5 threshold. F32 had no changed decisions on that set.&lt;/p&gt;

&lt;p&gt;A smaller model representation needs its own evaluation on the questions your app asks.&lt;/p&gt;

&lt;p&gt;Game tests cover rules, planning, candidate selection, and loading failures. Reference checks cover numerical agreement. Neither is replaced by a high game score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take the pattern into your app
&lt;/h2&gt;

&lt;p&gt;Load once at application startup, reuse the model for decisions, and dispose it when the owning feature closes. Keep model preparation out of the game’s per-piece loop.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://llamadart.leehack.com/docs/guides/decision-models" rel="noopener noreferrer"&gt;decision-model guide&lt;/a&gt; covers typed keys, loading options, and runtime support when you move beyond this example.&lt;/p&gt;

&lt;p&gt;Outside Tetris, the same pattern might choose a support category, route a request, or select among actions allowed by a simulation. Use clear descriptions and keep the available actions bounded by application state.&lt;/p&gt;

&lt;p&gt;The current Laya format has a 512-token sequence limit and a smaller shared question-and-option budget. Large descriptions can be truncated. Confidence is derived from the returned distribution; it is not a calibrated guarantee of correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try changing the player
&lt;/h2&gt;

&lt;p&gt;If the only goal were to build the strongest player in this comparison, the heuristic would be the obvious starting point.&lt;/p&gt;

&lt;p&gt;The progression from one line to 48 makes local specialization worth exploring. Training improved this narrow task without requiring a larger inference architecture. The practical question is whether that quality is useful within the target device’s latency budget, with a simple heuristic beside it to keep the comparison honest.&lt;/p&gt;

&lt;p&gt;Llamadart provides a way to explore that through Dart and Flutter, with a native llama.cpp path and a compatible WebGPU path. The measured M4 Max results should stay attached to that hardware and setup rather than becoming a claim about every phone or browser.&lt;/p&gt;

&lt;p&gt;Try the &lt;a href="https://leehack-flutter-laya-tetris.static.hf.space" rel="noopener noreferrer"&gt;Tetris demo&lt;/a&gt;, switch between the base and tuned players, and watch both the board and the decision panel. The failed moves are part of what makes the experiment worth looking at.&lt;/p&gt;

&lt;p&gt;Next in the series: working with local embeddings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete head comparison
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;bin/compare_heads.dart&lt;/code&gt; in your Dart project and run &lt;code&gt;dart run bin/compare_heads.dart&lt;/code&gt;. The candidates are illustrative, as in the first walkthrough.&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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:llamadart/llamadart.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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'fr0stbit3/laya-gguf'&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;revision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'ce2afdc0a8766af56a29a22dcf4a781e1f5c7d3c'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;compareHeads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;encoder:&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;huggingFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;repoId:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;revision:&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;filePath:&lt;/span&gt; &lt;span class="s"&gt;'laya-Q8_0.gguf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;baseHead:&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;huggingFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;repoId:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;revision:&lt;/span&gt; &lt;span class="n"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;filePath:&lt;/span&gt; &lt;span class="s"&gt;'laya-head.safetensors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;tunedHead:&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;huggingFace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;repoId:&lt;/span&gt; &lt;span class="s"&gt;'leehack/laya-tetris-head'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;revision:&lt;/span&gt; &lt;span class="s"&gt;'465546a595ee2e8e3b212b8cb16829205d5dfab6'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;filePath:&lt;/span&gt; &lt;span class="s"&gt;'laya-head-tetris.safetensors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;request:&lt;/span&gt; &lt;span class="n"&gt;DecisionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;state:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'game'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'tetris'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'piece'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'O'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;questions:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'move'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DecisionQuestion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;'Which placement of the falling Tetris piece is best?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;criteria:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 1 to 2: clears zero lines, no new holes, stack 5 rows, bumpiness 8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;'B'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 5 to 6: clears one line, no new holes, stack 3 rows, bumpiness 4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;'C'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'columns 9 to 10: clears zero lines, adds one hole, stack 6 rows, bumpiness 10'&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="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Illustrative candidate descriptions, not a captured game turn.&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;compareHeads&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt; &lt;span class="n"&gt;baseHead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;ModelSource&lt;/span&gt; &lt;span class="n"&gt;tunedHead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;DecisionRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&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;LlamaEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LlamaModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;params:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DecisionModelParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encoderModelParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;tuned&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;base&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;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;head:&lt;/span&gt; &lt;span class="n"&gt;baseHead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;tuned&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;DecisionEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;head:&lt;/span&gt; &lt;span class="n"&gt;tunedHead&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;baseResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemOneBatch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;request&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;tunedResults&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;tuned&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemOneBatch&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;request&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;'Base: &lt;/span&gt;&lt;span class="si"&gt;${baseResults.single.choices['move']!.probabilities}&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;'Tuned: &lt;/span&gt;&lt;span class="si"&gt;${tunedResults.single.choices['move']!.probabilities}&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tuned&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kd"&gt;base&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dart</category>
      <category>flutter</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Llama Village: a virtual world powered by local AI with llamadart</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Thu, 08 Oct 2026 22:34:10 +0000</pubDate>
      <link>https://dev.to/gde/llama-village-a-virtual-world-powered-by-local-ai-with-llamadart-3lmi</link>
      <guid>https://dev.to/gde/llama-village-a-virtual-world-powered-by-local-ai-with-llamadart-3lmi</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6bcgz2mc4jblzy26609.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6bcgz2mc4jblzy26609.gif" alt="Animated gameplay showing llamas conversing in the village while Dash flies nearby." width="480" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Llama Village in motion. The characters’ dialogue is generated by an on-device model.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/leehack/llama-village/releases/tag/v0.1.0-preview" rel="noopener noreferrer"&gt;Watch the full trailer on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to try building a 3D game with &lt;code&gt;flutter_scene&lt;/code&gt;. I was already working on llamadart, so I thought it would be fun to use local AI in the game too. My starting idea was fairly loose: make it look good, keep it simple, and put Dash, the Flutter mascot, in a scene with some llamas.&lt;/p&gt;

&lt;p&gt;I wanted to show different kinds of AI working together, including multiple characters acting on their own. But I did not have a game concept I liked yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a reason for the AI
&lt;/h2&gt;

&lt;p&gt;I spent quite a while on the visuals. I tried a hand-drawn look, asked for something closer to Borderlands, and eventually switched to a softer film style because the earlier renders kept looking glitchy. Meanwhile, we tried several game ideas. I was not happy with how they played.&lt;/p&gt;

&lt;p&gt;These were two of my messages in the Claude Code session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Btw the game isn't fun.”&lt;/p&gt;

&lt;p&gt;“What's the point of ai?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Eventually I asked for a village where the llamas would live and interact on their own. I wanted to give them personalities, see their thoughts and conversations, and control Dash to interact with them. That was the idea we went ahead with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farlsnabc1lhgyoi3pfzo.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farlsnabc1lhgyoi3pfzo.jpg" alt="An overhead view of the colourful huts, bakery, paths and pond at golden hour." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The village at golden hour, with the bakery in the centre and the festival stage in the foreground.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who lives here?
&lt;/h2&gt;

&lt;p&gt;Pip, Mo, June, Bramble, and Clover each have a personality, needs, relationships, goals, and things they know. They make plans, walk around the village, and stop to talk. A missing scarf, a bread rumour, a secret crush, and the upcoming Berry Festival give them things to care about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33qrb9nhyoj6jawmmsjn.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33qrb9nhyoj6jawmmsjn.jpg" alt="June says “Oh, real poems now. Tell me more about them.” while talking with Bramble." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;June and Bramble talking. Each line is written from the speaker’s situation and the conversation so far.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They do not all know the same things. In the inspector below, June knows that Mo knocked Pip’s scarf into the pond because she heard it from Mo. Other entries are things she saw herself. The game keeps track of both the information and how she got it.&lt;/p&gt;

&lt;p&gt;This is what I mean by a multi-agent system in this game. Each llama has its own knowledge, goals, and relationships. They use the same underlying language model, with different context for each character.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq7zf8knv1gc74ve28xqg.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq7zf8knv1gc74ve28xqg.jpg" alt="June’s inspector lists facts and marks whether she saw them or heard them from another llama." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The inspector shows what June knows and who told her.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How a conversation changes the village
&lt;/h2&gt;

&lt;p&gt;I wanted to try more than one kind of AI. The language model writes dialogue and thoughts. Embeddings help compare the meaning of the dialogue with facts the listener could have learned. An optional Laya decision model helps choose casual conversation topics from the options the game allows.&lt;/p&gt;

&lt;p&gt;Movement, needs, knowledge tracking, and endings are handled in code. After a conversation, the model reports which facts were mentioned and suggests changes to mood and friendship. The game checks the fact claims before accepting them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A llama knows → they talk → the game checks → a neighbour learns.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Secrets require explicit keyword evidence; embedding similarity alone is not enough.&lt;/p&gt;

&lt;p&gt;For example, imagine one llama passing along news. The speaker needs to know it first. The model writes the exchange, and the game checks whether the words support transferring that information. If accepted, it becomes something the listener knows and may discuss later.&lt;/p&gt;

&lt;p&gt;The llamas can keep walking and going about their day while the model is busy writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Dash
&lt;/h2&gt;

&lt;p&gt;You play as Dash. Click a llama and he flies over to visit. The game generates four things he could say while he is on the way. I specifically wanted selectable options here, so you can play without stopping to type a message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsv6yk8ihhjqnlyi84grr.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsv6yk8ihhjqnlyi84grr.jpg" alt="Dash visits Pip beside the pond. Four choices offer teasing, news, a compliment and gossip." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dash can tell Pip the bread rumour is false, or start another rumour about Mo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The game chooses the available intentions and facts; the model words the options for the character. You can encourage someone, help them, pass along news, or stir up gossip. The llama’s reaction and the resulting changes to trust or mood are governed by rules, while the model gives the reply its voice.&lt;/p&gt;

&lt;p&gt;Passing along a correction can help. Gossip can leave a llama believing something false. Those changes carry into later interactions and count toward the ending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the village in 3D
&lt;/h2&gt;

&lt;p&gt;I had a lot of fun building this with &lt;code&gt;flutter_scene&lt;/code&gt;. I kept asking for changes to the characters, especially making the llamas look different from one another and getting Dash’s face right. Then came the weather, animals, music, and cutscenes for skipping the night.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fysfui5h1kdcaqwphdsmt.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fysfui5h1kdcaqwphdsmt.jpg" alt="Fireflies glow around a pond with ducks, a small boat and warm lamps at night." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fireflies and lamps around the pond at night.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn9l150tpg4x4pyrhjg7g.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn9l150tpg4x4pyrhjg7g.jpg" alt="Clover and Bramble run along a village path in the rain." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Clover and Bramble heading home during the storm.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The models and the 3D renderer share the same machine, which makes performance a concern. The scene needs to keep running while text is generated. Some work can happen ahead of time, like writing Dash’s options during his flight.&lt;/p&gt;

&lt;p&gt;Llamadart provides the cross-platform local AI foundation. The game shown here is the macOS preview, running inference locally with the model files already on the machine. Bringing the same experience to other devices still requires working through their memory, inference, and rendering budgets.&lt;/p&gt;

&lt;p&gt;When I started thinking about mobile, I even asked whether we could make a version without an LLM. That is still an open design question. Generating everything live has a cost, and I would want to reconsider how much of it a mobile version needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A festival, then a storybook
&lt;/h2&gt;

&lt;p&gt;I also wanted the game to have a goal and different endings based on what Dash did. The Berry Festival gives the week a destination. The llamas perform, a winner is chosen, and the ending reflects the relationships, rumours, and trust built up during play. The winner and ending are determined by game rules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmh153bi3c5odrdw7q3w.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmh153bi3c5odrdw7q3w.jpg" alt="Mo stands in a warm spotlight beneath the Golden Bell, with June and Pip on stage." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mo wins the Golden Bell in this recorded playthrough.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Afterward, the game turns the week into a picture book. A journal records notable events, conversations, rumours, and Dash’s visits. The model uses a digest of those events to write the pages, alongside pictures captured during play.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0r9nsd6xqypi0onttxz.jpg" class="article-body-image-wrapper"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd0r9nsd6xqypi0onttxz.jpg" alt="An open picture book titled The First Day, with a village image and a narrative about the day." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first day, retold using the game’s journal and a picture captured during play.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This was something I asked for after the game had taken shape: a fairy-tale book about what happened during the run. It is a nice way to look back at conversations and events you may have missed while following Dash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I would take it next
&lt;/h2&gt;

&lt;p&gt;I would like to try a lighter version that works well on more devices. Llamadart’s cross-platform support gives me a starting point, but this game still needs its own performance work and testing on each platform.&lt;/p&gt;

&lt;p&gt;For this version, I got to build the experiment I wanted: several characters talking and acting in the same world, with local models doing different jobs. I also spent far more time adjusting llama faces and village lighting than the original idea suggested.&lt;/p&gt;

&lt;p&gt;If you try it, I would be interested to hear whether you prefer watching the llamas or getting involved as Dash.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/leehack/llama-village" rel="noopener noreferrer"&gt;Explore Llama Village on GitHub&lt;/a&gt; · &lt;a href="https://github.com/leehack/llama-village/releases/tag/v0.1.0-preview" rel="noopener noreferrer"&gt;Watch the preview trailer&lt;/a&gt; · &lt;a href="https://github.com/leehack/llamadart" rel="noopener noreferrer"&gt;Explore llamadart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Writing assistance: this article was drafted with AI assistance using my development notes and project documentation. Gameplay images and animation are from Llama Village.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>ai</category>
      <category>gamedev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Making cross-platform local AI easier with llamadart</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Thu, 08 Oct 2026 00:52:40 +0000</pubDate>
      <link>https://dev.to/gde/making-cross-platform-local-ai-easier-with-llamadart-2l4k</link>
      <guid>https://dev.to/gde/making-cross-platform-local-ai-easier-with-llamadart-2l4k</guid>
      <description>&lt;p&gt;My writing assistant could talk to Gemini. The missing piece was using it without a connection.&lt;/p&gt;

&lt;p&gt;That was the reason I started llamadart: I wanted an offline mode for flights, unreliable internet, and environments where outbound API access was unavailable. In &lt;a href="https://dev.to/gde/why-i-built-llamadart-offline-local-llm-inference-for-dartflutter-38pf"&gt;my first article&lt;/a&gt;, I focused on the work needed to get there: native binaries, Dart build hooks, and model chat templates.&lt;/p&gt;

&lt;p&gt;Getting a response from a local model is a satisfying milestone. It also leaves you with a surprisingly long list of application problems.&lt;/p&gt;

&lt;p&gt;Where does the model come from? What happens if someone cancels its download? Where does the conversation live? Can they switch models without losing the one that already works?&lt;/p&gt;

&lt;p&gt;Those questions are a useful way to explain how llamadart has changed. The project now does more, but the interesting part of 0.11 is how much of the ordinary integration work fits into a smaller, clearer API.&lt;/p&gt;

&lt;p&gt;Let's use a simple writing-assistant flow to walk through it: download a model, ask it to rewrite a paragraph, then ask for a shorter version. This is an illustrative app flow, not a claim about a particular model's writing quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before the first answer
&lt;/h2&gt;

&lt;p&gt;Imagine adding an “Offline mode” button to that application. Pressing it cannot immediately make a large model appear on the device.&lt;/p&gt;

&lt;p&gt;The app needs to explain the download, show progress, and recover when the user closes the screen. Later launches should reuse the file rather than download it again. These are the details a user encounters before they ever see a generated word.&lt;/p&gt;

&lt;p&gt;The native packaging work from the first article still helps here. App developers do not need a local C++ toolchain for the common setup; the package's build hook resolves the native runtime assets. Model files are a separate concern.&lt;/p&gt;

&lt;p&gt;In 0.11, the model-loading entrypoint brings those files together. A &lt;code&gt;LlamaModel&lt;/code&gt; describes the model and an optional multimodal projector. Its &lt;code&gt;ModelSource&lt;/code&gt; can be a local path, a URL, or a Hugging Face reference.&lt;/p&gt;

&lt;p&gt;The change is small enough to show directly. With &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;params&lt;/code&gt; already defined, the old setup looked like this:&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="c1"&gt;// 0.10&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlamaEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LlamaBackend&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadModelSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;modelParams:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Use the engine.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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;Now the ready engine comes back from the load:&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="c1"&gt;// 0.11&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&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;LlamaEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;LlamaModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kn"&gt;source&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;params:&lt;/span&gt; &lt;span class="n"&gt;params&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use the engine.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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 important difference is where ownership begins. If the new load throws, the engine and backend it created are already disposed. If it succeeds, the caller owns the ready engine.&lt;/p&gt;

&lt;p&gt;Progress and cancellation are still things the app needs to present. The loader exposes them through &lt;code&gt;onProgress&lt;/code&gt; and download options, so they can belong to the same operation as loading the model. &lt;a href="https://github.com/leehack/llamadart/blob/v0.11.0/website/docs/guides/model-lifecycle.md" rel="noopener noreferrer"&gt;The lifecycle guide&lt;/a&gt; covers that operation in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a prompt to a conversation
&lt;/h2&gt;

&lt;p&gt;Once the model is ready, our writing assistant can ask for a rewrite. Then comes the follow-up: “Make it shorter.”&lt;/p&gt;

&lt;p&gt;That short instruction depends on the earlier exchange. The app needs to keep the paragraph and the first answer in the conversation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ChatSession&lt;/code&gt; handles that history. It already existed before 0.11; the new convenience methods make the common case easier to read. Send a message with &lt;code&gt;session.send&lt;/code&gt;, then get the completed response through &lt;code&gt;reply.text&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is a complete native Dart starting point:&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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:llamadart/llamadart.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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&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;LlamaEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LlamaModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ModelSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;'hf://unsloth/SmolLM2-135M-Instruct-GGUF/'&lt;/span&gt;
        &lt;span class="s"&gt;'SmolLM2-135M-Instruct-Q2_K.gguf'&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;params:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ModelParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;contextSize:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;gpuLayers:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;systemPrompt:&lt;/span&gt; &lt;span class="s"&gt;'You help rewrite text concisely.'&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;reply&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;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'Rewrite this: We are writing to let you know '&lt;/span&gt;
      &lt;span class="s"&gt;'that the meeting will start at nine.'&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="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&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;shorter&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;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Make it shorter.'&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="n"&gt;shorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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;The tiny model keeps this example approachable. Choosing a model that follows your instructions well is a separate step, and you should evaluate it with your own inputs.&lt;/p&gt;

&lt;p&gt;For a live chat UI, &lt;code&gt;session.create&lt;/code&gt; still streams the response. Reading its text has changed from &lt;code&gt;chunk.choices.first.delta.content&lt;/code&gt; to &lt;code&gt;chunk.text&lt;/code&gt;. That is a small improvement you notice every time you connect the stream to a widget.&lt;/p&gt;

&lt;p&gt;There is a limit to conversation memory. &lt;code&gt;ChatSession&lt;/code&gt; trims older turns to fit the context budget while keeping the system prompt. If your app already owns and edits transcripts, &lt;code&gt;engine.create&lt;/code&gt; lets you supply the full message list yourself.&lt;/p&gt;

&lt;p&gt;These examples follow the tagged &lt;a href="https://github.com/leehack/llamadart/blob/v0.10.0/website/docs/getting-started/first-chat-session.md" rel="noopener noreferrer"&gt;0.10&lt;/a&gt; and &lt;a href="https://github.com/leehack/llamadart/blob/v0.11.0/website/docs/getting-started/first-chat-session.md" rel="noopener noreferrer"&gt;0.11&lt;/a&gt; documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model picker changes the problem
&lt;/h2&gt;

&lt;p&gt;Now imagine the writing assistant has a model picker. Someone wants to try a different model while the current one is available.&lt;/p&gt;

&lt;p&gt;An app could unload the current model first and then start downloading its replacement. On a slow connection, that leaves the user waiting without either model ready.&lt;/p&gt;

&lt;p&gt;On native targets, &lt;code&gt;setModel&lt;/code&gt; keeps the current model available while the replacement files resolve or download. If that stage fails or is cancelled, the old model stays loaded. Only after the files are ready does replacement begin.&lt;/p&gt;

&lt;p&gt;This has a boundary worth understanding: a failure during the later load can leave nothing loaded. Web runtimes also unload before fetching the replacement. The API gives the app a defined lifecycle, rather than a guarantee that every switch succeeds.&lt;/p&gt;

&lt;p&gt;The same ownership question appears when leaving the chat screen. A Flutter engine should live in a service, provider, or state object, not inside &lt;code&gt;build()&lt;/code&gt;. Dispose it when that owner is finished. Desktop app exit needs its own cleanup path where documented.&lt;/p&gt;

&lt;p&gt;These choices are less visible than a new model demo, but they determine whether the feature behaves sensibly when someone uses the rest of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  More runtimes without pretending they are identical
&lt;/h2&gt;

&lt;p&gt;The project has also grown beyond its original llama.cpp path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/leehack/llamadart/blob/v0.11.0/CHANGELOG.md" rel="noopener noreferrer"&gt;Version 0.7&lt;/a&gt; added LiteRT-LM support. Version 0.8 separated Apple runtime companions from the core package, keeping pure Dart consumers free of a Flutter SDK constraint. Version 0.10 added preview image generation through an opt-in stable-diffusion.cpp runtime.&lt;/p&gt;

&lt;p&gt;That creates more possibilities, along with more differences the application must respect.&lt;/p&gt;

&lt;p&gt;For our writing assistant, a useful question is whether to enable an image attachment button. The answer should come from the loaded model and runtime. In 0.11, &lt;code&gt;engine.runtime&lt;/code&gt; identifies that runtime, and &lt;code&gt;await engine.capabilities&lt;/code&gt; reports supported operations.&lt;/p&gt;

&lt;p&gt;Device selection follows the same principle. &lt;code&gt;ModelParams.device&lt;/code&gt; provides a shared CPU, GPU, or NPU selection. An explicit request runs there or fails as unsupported; &lt;code&gt;auto&lt;/code&gt; preserves the runtime's default.&lt;/p&gt;

&lt;p&gt;There are still platform limits. Android llama.cpp Vulkan is experimental and device-dependent. Web support is experimental. Automatic tool loops reject the pinned LiteRT-LM runtimes because they cannot reliably report token-limit truncation. The &lt;a href="https://llamadart.leehack.com/docs/platforms/support-matrix" rel="noopener noreferrer"&gt;support matrix&lt;/a&gt; is the place to check a deployment.&lt;/p&gt;

&lt;p&gt;The payoff for app code is a common way to ask what is available, and a clear failure when a requested operation cannot be supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try one useful offline feature
&lt;/h2&gt;

&lt;p&gt;To run the example, add &lt;code&gt;llamadart: ^0.11.0&lt;/code&gt; and run &lt;code&gt;dart pub get&lt;/code&gt; or &lt;code&gt;flutter pub get&lt;/code&gt;. The package requires Dart 3.10.7 or newer, and Flutter 3.38.0 or newer for Flutter apps.&lt;/p&gt;

&lt;p&gt;Flutter iOS and macOS apps using the GGUF Apple companion should pair it with &lt;code&gt;llamadart_llama_cpp_flutter: ^0.0.21&lt;/code&gt;. Other runtimes have their own companions and requirements; follow the &lt;a href="https://github.com/leehack/llamadart/blob/v0.11.0/README.md#install" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first setup needs a connection for missing runtime assets and model files. Native model downloads are cached for reuse. Once the required assets are present, inference can run locally; any remote tools or cloud services you add still need their own connections.&lt;/p&gt;

&lt;p&gt;For an existing app, read the &lt;a href="https://github.com/leehack/llamadart/blob/v0.11.0/MIGRATION.md" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;. Older model-loading methods remain deprecated until 1.0, but other API and lifecycle changes need attention.&lt;/p&gt;

&lt;p&gt;Start with one feature: a rewrite, a summary, or a conversation. Get it working, then try cancelling, switching models, and leaving the screen. Those interactions tell you much more about the integration than a successful first prompt.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/leehack/llamadart/tree/v0.11.0/example" rel="noopener noreferrer"&gt;Dart and Flutter examples&lt;/a&gt; are there to build on. I would welcome &lt;a href="https://github.com/leehack/llamadart/issues" rel="noopener noreferrer"&gt;feedback&lt;/a&gt; about the parts that still make that journey harder than it needs to be.&lt;/p&gt;

</description>
      <category>dart</category>
      <category>flutter</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why I Built llamadart: Offline Local LLM Inference for Dart/Flutter</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Sun, 15 Feb 2026 02:30:51 +0000</pubDate>
      <link>https://dev.to/gde/why-i-built-llamadart-offline-local-llm-inference-for-dartflutter-38pf</link>
      <guid>https://dev.to/gde/why-i-built-llamadart-offline-local-llm-inference-for-dartflutter-38pf</guid>
      <description>&lt;p&gt;I built a desktop AI-powered writing assistant, and cloud inference with &lt;code&gt;Gemini&lt;/code&gt; worked great.&lt;/p&gt;

&lt;p&gt;But I needed one more mode: offline AI.&lt;/p&gt;

&lt;p&gt;I wanted the app to work on flights, unstable connections, and restricted environments where outbound API access is unavailable. That pushed me toward local inference with &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; and GGUF models.&lt;/p&gt;

&lt;p&gt;The biggest friction was native packaging. Cross-platform C++ setup is powerful, but it usually means extra work before product work: toolchains, linker/runtime quirks, per-architecture builds, and CI maintenance.&lt;/p&gt;

&lt;p&gt;So I started &lt;code&gt;llamadart&lt;/code&gt; with one practical goal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make local LLM inference in Dart/Flutter usable without forcing app developers to manually build llama.cpp first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What llamadart is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/llamadart" rel="noopener noreferrer"&gt;llamadart&lt;/a&gt; is a Dart/Flutter package for local GGUF inference on top of &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;, with a Dart-first API and streaming support.&lt;/p&gt;

&lt;p&gt;Core references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package: &lt;a href="https://pub.dev/packages/llamadart" rel="noopener noreferrer"&gt;https://pub.dev/packages/llamadart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/leehack/llamadart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Main docs: &lt;a href="https://github.com/leehack/llamadart/blob/main/README.md" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;High-level structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App Layer (Flutter UI / CLI / API server)
        |
        v
LlamaEngine (stateless orchestration)
        |
        +--&amp;gt; ChatSession (stateful history + context trimming)
        |
        +--&amp;gt; ChatTemplateEngine (detect/render/parse/tool grammar)
        |
        v
LlamaBackend abstraction
        |
        +--&amp;gt; NativeLlamaBackend (isolate + FFI)
        |
        +--&amp;gt; Web backend (bridge runtime path)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LlamaEngine&lt;/code&gt;: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/core/engine/engine.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/core/engine/engine.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ChatSession&lt;/code&gt;: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/core/engine/chat_session.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/core/engine/chat_session.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ChatTemplateEngine&lt;/code&gt;: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/core/template/chat_template_engine.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/core/template/chat_template_engine.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LlamaBackend&lt;/code&gt; interface: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/backends/backend.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/backends/backend.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Native backend: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/backends/llama_cpp/llama_cpp_backend.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/backends/llama_cpp/llama_cpp_backend.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Web backend: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/backends/webgpu/webgpu_backend.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/backends/webgpu/webgpu_backend.dart&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build hook deep dive: how native binaries are delivered
&lt;/h2&gt;

&lt;p&gt;This is the part I cared about most.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llamadart&lt;/code&gt; uses Dart build hooks (official docs: &lt;a href="https://dart.dev/tools/hooks" rel="noopener noreferrer"&gt;https://dart.dev/tools/hooks&lt;/a&gt;). Build hooks are executed automatically by the Dart SDK during &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;test&lt;/code&gt; to compile or download native assets.&lt;/p&gt;

&lt;p&gt;For FFI context, Dart also documents this under C interop: &lt;a href="https://dart.dev/interop/c-interop#build-hooks" rel="noopener noreferrer"&gt;https://dart.dev/interop/c-interop#build-hooks&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook entry point
&lt;/h3&gt;

&lt;p&gt;The hook implementation is here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/leehack/llamadart/blob/main/hook/build.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/hook/build.dart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;build.dart&lt;/code&gt; reads target metadata from hook input (&lt;code&gt;BuildInput&lt;/code&gt;) and reports resolved assets through &lt;code&gt;BuildOutputBuilder&lt;/code&gt; as &lt;code&gt;CodeAsset&lt;/code&gt; entries.&lt;/p&gt;

&lt;p&gt;Related official APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hooks package: &lt;a href="https://pub.dev/packages/hooks" rel="noopener noreferrer"&gt;https://pub.dev/packages/hooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BuildInput&lt;/code&gt; and &lt;code&gt;BuildOutputBuilder&lt;/code&gt;: &lt;a href="https://pub.dev/documentation/hooks/latest/hooks/" rel="noopener noreferrer"&gt;https://pub.dev/documentation/hooks/latest/hooks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code assets package: &lt;a href="https://pub.dev/packages/code_assets" rel="noopener noreferrer"&gt;https://pub.dev/packages/code_assets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CodeAsset&lt;/code&gt; API: &lt;a href="https://pub.dev/documentation/code_assets/latest/code_assets/CodeAsset-class.html" rel="noopener noreferrer"&gt;https://pub.dev/documentation/code_assets/latest/code_assets/CodeAsset-class.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What happens at build/run time
&lt;/h3&gt;

&lt;p&gt;The hook runs a deterministic local-first pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resolve target platform and architecture from hook input.&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;(os, arch)&lt;/code&gt; to a concrete release asset filename (for example, &lt;code&gt;libllamadart-linux-x64.so&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Try local packaged binary (&lt;code&gt;third_party/bin/...&lt;/code&gt;) first.&lt;/li&gt;
&lt;li&gt;If missing, try cache (&lt;code&gt;.dart_tool/llamadart/binaries/...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If cache miss, download from GitHub Releases.&lt;/li&gt;
&lt;li&gt;Copy to hook output directory with standardized runtime filename.&lt;/li&gt;
&lt;li&gt;Report the asset as &lt;code&gt;DynamicLoadingBundled()&lt;/code&gt; so Dart can bundle and load it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On Apple targets, the hook also handles binary thinning when needed (&lt;code&gt;lipo&lt;/code&gt;) so the reported artifact matches the active architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  How GitHub is used
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;llamadart&lt;/code&gt; uses GitHub Releases as the binary distribution layer.&lt;/p&gt;

&lt;p&gt;In the hook, the release URL is pinned by a llama.cpp tag constant:&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;const&lt;/span&gt; &lt;span class="n"&gt;_llamaCppTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'b8011'&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;_baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="s"&gt;'https://github.com/leehack/llamadart/releases/download/&lt;/span&gt;&lt;span class="si"&gt;$_llamaCppTag&lt;/span&gt;&lt;span class="s"&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 gives stable, tag-based binary resolution instead of "latest" behavior.&lt;/p&gt;

&lt;p&gt;The automation that produces those release assets is here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/leehack/llamadart/blob/main/.github/workflows/llama_cpp_auto.yml" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/.github/workflows/llama_cpp_auto.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That workflow builds artifacts across target OS/arch matrices, uploads release assets, regenerates bindings when needed, and updates the pinned hook tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime FFI resolution
&lt;/h3&gt;

&lt;p&gt;At runtime, FFI symbols are linked to the hook-provided asset via annotations like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Native&lt;/code&gt;: &lt;a href="https://api.dart.dev/dart-ffi/Native-class.html" rel="noopener noreferrer"&gt;https://api.dart.dev/dart-ffi/Native-class.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@DefaultAsset&lt;/code&gt;: &lt;a href="https://api.dart.dev/dart-ffi/DefaultAsset-class.html" rel="noopener noreferrer"&gt;https://api.dart.dev/dart-ffi/DefaultAsset-class.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In &lt;code&gt;llamadart&lt;/code&gt;, generated bindings use this default asset pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/backends/llama_cpp/bindings.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/backends/llama_cpp/bindings.dart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That avoids per-platform dynamic library path logic in app code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters in practice
&lt;/h3&gt;

&lt;p&gt;This hook + release model gives a practical DX benefit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Package users avoid manual local llama.cpp build steps in the common case.&lt;/li&gt;
&lt;li&gt;First build can fetch the correct binary for the target.&lt;/li&gt;
&lt;li&gt;Subsequent builds use cache/local artifacts.&lt;/li&gt;
&lt;li&gt;App code stays focused on model lifecycle and inference logic, not native packaging scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A quick note on &lt;a href="https://pub.dev/packages/dinja" rel="noopener noreferrer"&gt;dinja&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;While implementing chat-template compatibility, I ran into another gap: GGUF models expose Jinja templates, but template behavior differs significantly across model families.&lt;/p&gt;

&lt;p&gt;I could not find a Dart Jinja package tuned for this multi-template LLM use case, so I built &lt;a href="https://pub.dev/packages/dinja" rel="noopener noreferrer"&gt;dinja&lt;/a&gt; (source: &lt;a href="https://github.com/leehack/dinja" rel="noopener noreferrer"&gt;https://github.com/leehack/dinja&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;llamadart&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Template handlers render prompts with &lt;code&gt;dinja&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JinjaAnalyzer&lt;/code&gt; does AST-level capability checks (system role, tool-call shape, typed content, thinking tags).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ChatTemplateEngine&lt;/code&gt; routes handlers and supports custom overrides/registration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relevant links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handlers directory: &lt;a href="https://github.com/leehack/llamadart/tree/main/lib/src/core/template/handlers" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/tree/main/lib/src/core/template/handlers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Jinja analyzer: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/core/template/jinja/jinja_analyzer.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/core/template/jinja/jinja_analyzer.dart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Template engine: &lt;a href="https://github.com/leehack/llamadart/blob/main/lib/src/core/template/chat_template_engine.dart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/blob/main/lib/src/core/template/chat_template_engine.dart&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minimal usage example
&lt;/h2&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;'dart:io'&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:llamadart/llamadart.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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LlamaEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LlamaBackend&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'path/to/model.gguf'&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;systemPrompt:&lt;/span&gt; &lt;span class="s"&gt;'You are a helpful writing assistant.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&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;chunk&lt;/span&gt; &lt;span class="k"&gt;in&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;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="n"&gt;LlamaTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Rewrite this paragraph in a concise style.'&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="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt; &lt;span class="o"&gt;??&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;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&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;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I built &lt;code&gt;llamadart&lt;/code&gt; because my app needed both online model quality and reliable offline execution, without turning native build management into the main project.&lt;/p&gt;

&lt;p&gt;If you are building AI features in Dart/Flutter and need local inference, I would love feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/llamadart" rel="noopener noreferrer"&gt;https://pub.dev/packages/llamadart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/leehack/llamadart" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/leehack/llamadart/issues" rel="noopener noreferrer"&gt;https://github.com/leehack/llamadart/issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>dart</category>
      <category>llama</category>
      <category>flutter</category>
    </item>
    <item>
      <title>How to use GoLang in Flutter Application - Golang FFI</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Wed, 20 Dec 2023 02:35:14 +0000</pubDate>
      <link>https://dev.to/leehack/how-to-use-golang-in-flutter-application-golang-ffi-1950</link>
      <guid>https://dev.to/leehack/how-to-use-golang-in-flutter-application-golang-ffi-1950</guid>
      <description>&lt;p&gt;GoLang is a very powerful language that is easy to use. Writing a GoLang program and compiling it to a binary file is very easy. Golang is very useful in writing high-performance and low-latency applications, but it needs improvement in GUI. Flutter is a compelling cross-platform UI framework. It allows you to write a beautiful application effortlessly. Mixing GoLang and Flutter is an excellent idea to write a high-performance and beautiful application.&lt;/p&gt;

&lt;p&gt;In this article, we will see how to compile a GoLang-based library and create a Flutter plugin package for a Flutter application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is FFI?
&lt;/h2&gt;

&lt;p&gt;FFI stands for Foreign Function Interface. It is a way to call functions written in other languages from Dart. FFI helps use libraries written in other languages in a Flutter application. Flutter supports FFI for Android, iOS, Linux, macOS, and Windows. In this article, we will see how to use FFI.&lt;/p&gt;

&lt;p&gt;In Dart, the programming language used to write Flutter applications, we can use the &lt;code&gt;dart:ffi&lt;/code&gt; package to use FFI. The &lt;code&gt;dart:ffi&lt;/code&gt; package is used to call functions written in other languages from Dart. We can use the &lt;code&gt;dart:ffi&lt;/code&gt; package to call functions written in C, C++, or Rust. In this article, we will see how to use the &lt;code&gt;dart:ffi&lt;/code&gt; package to call functions in GoLang.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a plugin project for Flutter
&lt;/h2&gt;

&lt;p&gt;The Flutter plugin can contain Dart and platform-specific code written in Kotlin, Swift, Objective-C, or GoLang. We can use a Flutter plugin to add functionality to a Flutter application.&lt;/p&gt;

&lt;p&gt;The below command will create a Flutter plugin project named &lt;code&gt;native_add&lt;/code&gt;. The &lt;code&gt;native_add&lt;/code&gt; project will support Android, iOS, Linux, macOS, and Windows. The &lt;code&gt;native_add&lt;/code&gt; project will have a &lt;code&gt;lib&lt;/code&gt; folder that contains the Dart code and an &lt;code&gt;example&lt;/code&gt; folder that contains a Flutter application to test the plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nv"&gt;$ &lt;/span&gt;flutter create &lt;span class="nt"&gt;--platforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;android,ios,macos,windows,linux &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plugin_ffi native_add
 &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;native_add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a GoLang library
&lt;/h2&gt;

&lt;p&gt;The plugin project we created in the previous step will have a &lt;code&gt;src&lt;/code&gt; folder. The &lt;code&gt;src&lt;/code&gt; folder will have a &lt;code&gt;cpp&lt;/code&gt; folder that contains the C++ code. We will create a GoLang library in the &lt;code&gt;src&lt;/code&gt; folder. The GoLang library will have a function to add two numbers.&lt;/p&gt;

&lt;p&gt;First, we will create a &lt;code&gt;go.mod&lt;/code&gt; file. The &lt;code&gt;go.mod&lt;/code&gt; file defines the module name and the GoLang version. The &lt;code&gt;go.mod&lt;/code&gt; file is used to manage dependencies. We will not use any dependencies in this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// go.mod file&lt;/span&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;go_code&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.21.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, we will create a &lt;code&gt;sum.go&lt;/code&gt; file. The &lt;code&gt;sum.go&lt;/code&gt; file will have a function to add two numbers. The &lt;code&gt;sum.go&lt;/code&gt; file will also have a function called &lt;code&gt;enforce_binding&lt;/code&gt;. The &lt;code&gt;enforce_binding&lt;/code&gt; function enforces the binding in the ios application. The Dart code will not use the &lt;code&gt;enforce_binding&lt;/code&gt; function, but it was the trick I found to make the library linked correctly for iOS.&lt;/p&gt;

&lt;p&gt;Above each function we want to expose, we will add a comment &lt;code&gt;//export &amp;lt;function_name&amp;gt;&lt;/code&gt;. The &lt;code&gt;//export &amp;lt;function_name&amp;gt;&lt;/code&gt; comment exposes the function to other languages. We must also use the type &lt;code&gt;C.int&lt;/code&gt; instead of &lt;code&gt;int&lt;/code&gt; in the function signature. The &lt;code&gt;C.int&lt;/code&gt; type is used to make the function compatible with other languages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// sum.go file&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;

&lt;span class="c"&gt;//export sum&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//export enforce_binding&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;enforce_binding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&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;h3&gt;
  
  
  Compile the GoLang library for Android
&lt;/h3&gt;

&lt;p&gt;Since we are compiling the GoLang library for Android, we must use cross-compilation. Modern Android devices mostly use &lt;code&gt;arm64&lt;/code&gt; or &lt;code&gt;x86_64&lt;/code&gt; architectures. We will compile the GoLang library for both &lt;code&gt;arm64&lt;/code&gt; and &lt;code&gt;x86_64&lt;/code&gt; architectures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../android/src/main/jniLibs
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_SDK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;HOME&lt;span class="si"&gt;)&lt;/span&gt;/Library/Android/sdk
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NDK_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ANDROID_SDK&lt;span class="si"&gt;)&lt;/span&gt;/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin

&lt;span class="c"&gt;# Compile for x86_64 architecture and place the binary file in the android/src/main/jniLibs/x86_64 folder&lt;/span&gt;
&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;android &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;NDK_BIN&lt;span class="si"&gt;)&lt;/span&gt;/x86_64-linux-android21-clang &lt;span class="se"&gt;\&lt;/span&gt;
go build &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c-shared &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ANDROID_OUT&lt;span class="si"&gt;)&lt;/span&gt;/x86_64/libsum.so &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Compile for arm64 architecture and place the binary file in the android/src/main/jniLibs/arm64-v8a folder&lt;/span&gt;
&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;android &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;NDK_BIN&lt;span class="si"&gt;)&lt;/span&gt;/aarch64-linux-android21-clang &lt;span class="se"&gt;\&lt;/span&gt;
go build &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c-shared &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ANDROID_OUT&lt;span class="si"&gt;)&lt;/span&gt;/arm64-v8a/libsum.so &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the &lt;code&gt;libsum&lt;/code&gt; library in the Dart code using FFI.&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;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'libsum.so'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compile the GoLang library for iOS
&lt;/h3&gt;

&lt;p&gt;We need some extra steps to cross-compile the Golang library and use it for iOS. iOS only supports static libraries. So, we need to compile the GoLang library as a static library. We also need to modify the podspec file to use the static library in the Flutter plugin. Another thing to note is that we need to create &lt;code&gt;xcframework&lt;/code&gt; for iOS. The &lt;code&gt;xcframework&lt;/code&gt; is used to support both simulator and device architectures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build_ios.sh file&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ios
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="c"&gt;# export CGO_CFLAGS="-fembed-bitcode"&lt;/span&gt;
&lt;span class="c"&gt;# export MIN_VERSION=15&lt;/span&gt;

&lt;span class="nv"&gt;SDK_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xcrun &lt;span class="nt"&gt;--sdk&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SDK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--show-sdk-path&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOARCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"amd64"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;CARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"x86_64"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GOARCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arm64"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;CARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"arm64"&lt;/span&gt;
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SDK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"iphoneos"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CARCH&lt;/span&gt;&lt;span class="s2"&gt;-apple-ios&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SDK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"iphonesimulator"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CARCH&lt;/span&gt;&lt;span class="s2"&gt;-apple-ios&lt;/span&gt;&lt;span class="nv"&gt;$MIN_VERSION&lt;/span&gt;&lt;span class="s2"&gt;-simulator"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;CLANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xcrun &lt;span class="nt"&gt;--sdk&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SDK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--find&lt;/span&gt; clang&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLANG&lt;/span&gt;&lt;span class="s2"&gt; -target &lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt; -isysroot &lt;/span&gt;&lt;span class="nv"&gt;$SDK_PATH&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;CC

go build &lt;span class="nt"&gt;-trimpath&lt;/span&gt; &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c-archive &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SDK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above script will create a static library for iOS. We must use the script to build the static library for both simulator and device architectures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="c"&gt;# Compile for x86_64 simulator architecture&lt;/span&gt;
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iphonesimulator &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;libsum &lt;span class="se"&gt;\&lt;/span&gt;
./build_ios.sh

&lt;span class="c"&gt;# Compile for arm64 simulator architecture&lt;/span&gt;
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iphonesimulator &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;libsum &lt;span class="se"&gt;\&lt;/span&gt;
./build_ios.sh

&lt;span class="c"&gt;# Compile for arm64 device architecture&lt;/span&gt;
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;SDK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iphoneos &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;LIB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;libsum &lt;span class="se"&gt;\&lt;/span&gt;
./build_ios.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above script will create three static libraries. Before creating the &lt;code&gt;xcframework&lt;/code&gt;, we must combine the two static libraries for the simulator architecture. The &lt;code&gt;lipo&lt;/code&gt; command can combine the two static libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lipo &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-create&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
libsum_arm64_iphonesimulator.a &lt;span class="se"&gt;\&lt;/span&gt;
libsum_amd64_iphonesimulator.a &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-output&lt;/span&gt; libsum_iphonesimulator.a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can create the &lt;code&gt;xcframework&lt;/code&gt; using the below command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcodebuild &lt;span class="nt"&gt;-create-xcframework&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-output&lt;/span&gt; ../ios/libsum.xcframework &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-library&lt;/span&gt; ios-arm64/libsum.a &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-headers&lt;/span&gt; ios-arm64/libsum.h &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-library&lt;/span&gt; ios-simulator/libsum.a &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-headers&lt;/span&gt; ios-simulator/libsum.h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;libsum.xcframework&lt;/code&gt; will be created in the ios folder.&lt;/p&gt;

&lt;p&gt;We need to modify the &lt;code&gt;podspec&lt;/code&gt; file to use the &lt;code&gt;xcframework&lt;/code&gt;. We must add the lines below to the &lt;code&gt;podspec&lt;/code&gt; file under the &lt;code&gt;ios&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_header_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Classes/**/*.h'&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vendored_frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'libsum.xcframework'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some trial and error, I found that we need to use the library in the &lt;code&gt;Classes&lt;/code&gt; folder to link it properly. So, we create &lt;code&gt;binding.h&lt;/code&gt; and &lt;code&gt;EnforceBinding.swift&lt;/code&gt; files under the &lt;code&gt;Classes&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;binding.h&lt;/code&gt; file will have the below code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;enforce_binding&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;EnforceBinding.swift&lt;/code&gt; file will have the below code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;dummyMethodToEnforceBundling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;enforce_binding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// disable tree shaking&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use the &lt;code&gt;libsum&lt;/code&gt; library in the Dart code using FFI.&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="c1"&gt;// The libsum library is statically linked in the native_add so we only need to load native_add.&lt;/span&gt;
&lt;span class="n"&gt;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$native_add&lt;/span&gt;&lt;span class="s"&gt;.framework/native_add'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a binding in Dart using &lt;code&gt;ffigen&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ffigen&lt;/code&gt; tool generates the Dart code to use the GoLang library. We will create a &lt;code&gt;ffigen.yaml&lt;/code&gt; file to configure the &lt;code&gt;ffigen&lt;/code&gt; tool. The &lt;code&gt;config.yaml&lt;/code&gt; file will have the below code.&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;# Run with `flutter pub run ffigen --config ffigen.yaml`.&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NativeLibrary&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bindings to `src/sum.h`.&lt;/span&gt;
&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lib/generated_bindings.dart'&lt;/span&gt;
&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;entry-points&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/libsum.h'&lt;/span&gt;
&lt;span class="na"&gt;preamble&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;// ignore_for_file: always_specify_types&lt;/span&gt;
  &lt;span class="s"&gt;// ignore_for_file: camel_case_types&lt;/span&gt;
  &lt;span class="s"&gt;// ignore_for_file: non_constant_identifier_names&lt;/span&gt;
  &lt;span class="s"&gt;// ignore_for_file: unused_field&lt;/span&gt;
  &lt;span class="s"&gt;// ignore_for_file: unused_element&lt;/span&gt;
&lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;any&lt;/span&gt;
  &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can run the &lt;code&gt;ffigen&lt;/code&gt; tool to generate the Dart code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub run ffigen &lt;span class="nt"&gt;--config&lt;/span&gt; ffigen.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ffigen&lt;/code&gt; tool will generate the &lt;code&gt;generated_bindings.dart&lt;/code&gt; file under the &lt;code&gt;lib&lt;/code&gt; folder based on the &lt;code&gt;libsum.h&lt;/code&gt; file in &lt;code&gt;src&lt;/code&gt; folder that was generated by the &lt;code&gt;go build&lt;/code&gt; command in the previous step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the GoLang library in Dart
&lt;/h2&gt;

&lt;p&gt;Now, we can use the GoLang library in the Dart code. We can use the &lt;code&gt;DynamicLibrary.open&lt;/code&gt; method to load the library. We can use the &lt;code&gt;NativeLibrary&lt;/code&gt; class to call the functions in the GoLang library.&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="c1"&gt;// lib/native_add.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:ffi'&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;'dart:io'&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;'generated_bindings.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&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;_bindings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;_libName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'native_add'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// The dynamic library in which the symbols for [NativeAddBindings] can be found.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;DynamicLibrary&lt;/span&gt; &lt;span class="n"&gt;_dylib&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isMacOS&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isIOS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$_libName&lt;/span&gt;&lt;span class="s"&gt;.framework/&lt;/span&gt;&lt;span class="si"&gt;$_libName&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAndroid&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isLinux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'libsum.so'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&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;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isWindows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$_libName&lt;/span&gt;&lt;span class="s"&gt;.dll'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;UnsupportedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Unknown platform: &lt;/span&gt;&lt;span class="si"&gt;${Platform.operatingSystem}&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="c1"&gt;/// The bindings to the native functions in [_dylib].&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt; &lt;span class="n"&gt;_bindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NativeLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_dylib&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use the &lt;code&gt;sum&lt;/code&gt; function in the Dart code from the Flutter project that uses this plugin.&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="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:native_add/native_add.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;native_add&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;native_add&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we checked how to use GoLang in a Flutter application. We created a GoLang library and compiled it for Android and iOS. We also created a Flutter plugin to use the GoLang library in a Flutter application. We used the &lt;code&gt;ffigen&lt;/code&gt; tool to generate the Dart code to use the GoLang library. We also checked how to use the GoLang library in the Dart code.&lt;/p&gt;

&lt;p&gt;I also created a sample project to demonstrate using GoLang in a Flutter application. You can find the sample project in the below link. The example also contains the script to compile the GoLang library for MacOS. &lt;br&gt;
&lt;a href="https://github.com/leehack/flutter_golang_ffi_example"&gt;https://github.com/leehack/flutter_golang_ffi_example&lt;/a&gt;&lt;br&gt;
It's still missing for Windows and Linux but it should be much easier than for Android and iOS. Like Android or iOS, we must cross-compile and link the GoLang library for Windows and Linux in the CMake file.&lt;/p&gt;

&lt;p&gt;Due to the lack of information, I had to spend a lot of time figuring out how to use GoLang in a Flutter application. This article will help you to use GoLang in a Flutter application.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>go</category>
      <category>ffi</category>
      <category>dart</category>
    </item>
    <item>
      <title>Let's build a full-stack dart app</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Thu, 20 Jul 2023 20:33:57 +0000</pubDate>
      <link>https://dev.to/leehack/lets-build-a-full-stack-dart-app-36lm</link>
      <guid>https://dev.to/leehack/lets-build-a-full-stack-dart-app-36lm</guid>
      <description>&lt;p&gt;I've been working with Flutter for a while and love it. It's an excellent tool for building cross-platform applications. But what if you want to build a full-stack application? We will explore how to build a full-stack application using Flutter and Dart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fj544h5zyvtakq5man43x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fj544h5zyvtakq5man43x.gif" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete example code: &lt;a href="https://github.com/leehack/fullstack_dart_todo" rel="noopener noreferrer"&gt;https://github.com/leehack/fullstack_dart_todo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What will we build?
&lt;/h2&gt;

&lt;p&gt;The todo application with Flutter and Dart! We will use Flutter for the client and Dart Frog for the server. Flutter will display the UI, and Dart Frog will handle the business logic and data storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why full-stack in Dart?
&lt;/h2&gt;

&lt;p&gt;The most significant benefit of using Dart for full-stack development is that you can use the same language on both the client and server. It means that you don't have to learn two different languages, which makes it easier to get started with full-stack development. Of course, you can also share your code between the client and server, which is a huge benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;We will use the mono-repo structure for this project. We will create three packages: &lt;code&gt;backend&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt;, and &lt;code&gt;shared&lt;/code&gt;. &lt;code&gt;shared&lt;/code&gt; package will contain all the code that is shared between the client and server. &lt;code&gt;backend&lt;/code&gt; package will contain all the code for the server(Dart Frog) and &lt;code&gt;frontend&lt;/code&gt; package will contain all the code for the client(Flutter).&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared package (packages/todo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;todo_entity.dart&lt;/code&gt; - The Todo data model with the business logic.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;todo_repository.dart&lt;/code&gt; - The repository interface. The backend will implement it for storing data. The frontend will implement it for communicating with the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend package (packages/backend_frog)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;routes/todos/index.dart&lt;/code&gt; - The rest api handlers for the route &lt;code&gt;https://address.com/todos&lt;/code&gt;. It handles the GET and POST requests.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;routes/todos/[id].dart&lt;/code&gt; - The rest api handlers for the route &lt;code&gt;https://address.com/todos/[id]&lt;/code&gt;. It handles each todo item's GET/PUT/DELETE requests.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/data/in_memory_todo_repository.dart&lt;/code&gt; - The repository implementation of the in-memory database.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;routes/todos/_middleware.dart&lt;/code&gt; - Dependency Injection of the in-memory database that handlers will use.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend package (packages/frontend_flutter)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data/remote_todo_repository.dart&lt;/code&gt; - The repository implementation of the remote database (rest api client).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;providers/todo_list_provider.dart&lt;/code&gt; - The riverpod provider handles business logic and data storage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;presentation&lt;/code&gt; folder - The UI code for widgets.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages/todo_page.dart&lt;/code&gt; - The todo page displays the todo list and form.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mono-repo setup
&lt;/h2&gt;

&lt;p&gt;We will use &lt;a href="https://melos.invertase.dev/" rel="noopener noreferrer"&gt;melos&lt;/a&gt; to manage the mono-repo. Melos is a tool for managing Dart projects with multiple packages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install melos cli&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    dart pub global activate melos


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Create a project folder and initialize the melos workspace&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    &lt;span class="nb"&gt;mkdir &lt;/span&gt;fullstack_dart_todo
    &lt;span class="nb"&gt;cd &lt;/span&gt;fullstack_dart_todo
    &lt;span class="nb"&gt;mkdir &lt;/span&gt;packages


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the following to the &lt;code&gt;pubspec.yaml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;remote_todo&lt;/span&gt;

    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=2.18.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;3.0.0'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the melos as a development dependency:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    dart pub add melos &lt;span class="nt"&gt;--dev&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the following to the &lt;code&gt;melos.yaml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;remote_todo&lt;/span&gt;

    &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;packages/*&lt;/span&gt;

    &lt;span class="na"&gt;scripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;analyze&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dart analyze .&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now you can run the &lt;code&gt;melos bs&lt;/code&gt; for bootstrapping the mono-repo. Bootstrapping has two primary roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installing all package dependencies (internally using &lt;code&gt;pub get&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Locally linking any packages together.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Every time creating a new package, you must run the &lt;code&gt;melos bs&lt;/code&gt; command. If you clone the project from GitHub, you must run the &lt;code&gt;melos bs&lt;/code&gt; command.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the backend package with the Dart Frog
&lt;/h2&gt;

&lt;p&gt;We'll use the &lt;a href="https://dartfrog.vgv.dev/" rel="noopener noreferrer"&gt;Dart Frog&lt;/a&gt; framework for the backend. &lt;a href="https://dartfrog.vgv.dev/" rel="noopener noreferrer"&gt;Dart Frog&lt;/a&gt; is built on top of &lt;a href="https://pub.dev/packages/shelf" rel="noopener noreferrer"&gt;shelf&lt;/a&gt; and &lt;a href="https://pub.dev/packages/mason" rel="noopener noreferrer"&gt;mason&lt;/a&gt; and is inspired by many tools, including &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;remix.run&lt;/a&gt;, &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;next.js&lt;/a&gt;, and &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;express.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We need to install the &lt;code&gt;dart_frog_cli&lt;/code&gt; first.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    dart pub global activate dart_frog_cli


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we are ready to create the backend project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    &lt;span class="nb"&gt;cd &lt;/span&gt;packages
    dart_frog create backend_frog
    &lt;span class="nb"&gt;cd &lt;/span&gt;backend_frog


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Start the dev server:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    dart_frog dev


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By default, it opens the web server at &lt;code&gt;http://localhost:8080&lt;/code&gt;. You can see the default page. Thanks to the hot reload, you can see the changes immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the frontend package with the Flutter
&lt;/h2&gt;

&lt;p&gt;We'll use &lt;a href="https://flutter.dev/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; for the frontend. Flutter is Google's UI toolkit for building beautiful, natively compiled mobile, web, and desktop applications from a single codebase.&lt;/p&gt;

&lt;p&gt;Let's create the Flutter project under the packages folder.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    &lt;span class="nb"&gt;cd &lt;/span&gt;packages
    flutter create frontend_flutter
    &lt;span class="nb"&gt;cd &lt;/span&gt;frontend_flutter


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Create the pure Dart shared package
&lt;/h2&gt;

&lt;p&gt;The shared package will contain all the code that is shared between the client and server. We will create the pure dart package since it has no UI code.&lt;/p&gt;

&lt;p&gt;Let's create the library project under the packages folder.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

    &lt;span class="nb"&gt;cd &lt;/span&gt;packages
    dart create &lt;span class="nt"&gt;-t&lt;/span&gt; package todo
    &lt;span class="nb"&gt;cd &lt;/span&gt;todo


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To make the backend and frontend projects use the shared package, we must add the dependency to each project's &lt;code&gt;pubspec.yaml&lt;/code&gt; file (&lt;code&gt;backend_frog&lt;/code&gt;, &lt;code&gt;frontend_flutter&lt;/code&gt;).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

    &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;todo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.0.1&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we run the &lt;code&gt;melos bs&lt;/code&gt; command, the shared package will be linked to the backend and frontend project.&lt;/p&gt;

&lt;p&gt;So, now we have everything ready to start the coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fsh7equ0x8hneg1o1rumn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fsh7equ0x8hneg1o1rumn.png" alt="Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The shared package has the Todo data model and the repository interface. The backend and frontend packages implement the repository interface. The backend implements it as the in-memory database, and the frontend implements it as the rest api client. The Todo entity is the data model with the business logic that will be referenced in both the backend and frontend packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;The backend is a simple CRUD application that exposes REST APIs. It uses the in-memory database for storing data. The in-memory database is a simple List that stores the Todo data model. The backend has two routes: &lt;code&gt;https://address.com/todos&lt;/code&gt; and &lt;code&gt;https://address.com/todos/[id]&lt;/code&gt;. The first route handles the GET and POST requests. The second route handles each todo item's GET/PUT/DELETE requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routes
&lt;/h3&gt;

&lt;p&gt;Dart Frog uses directory-based routing. Each route is a directory with the &lt;code&gt;*.dart&lt;/code&gt; file. &lt;code&gt;index.dart&lt;/code&gt; represents the root route of the path. For example, the &lt;code&gt;routes/todos/index.dart&lt;/code&gt; represents the &lt;code&gt;https://address.com/todos&lt;/code&gt; route. The &lt;code&gt;routes/todos/[id].dart&lt;/code&gt; represents the &lt;code&gt;https://address.com/todos/[id]&lt;/code&gt; route.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handlers for &lt;code&gt;/todos&lt;/code&gt; in &lt;code&gt;routes/todos/index.dart&lt;/code&gt;
```dart
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future onRequest(RequestContext context) async {&lt;br&gt;
    return switch (context.request.method) {&lt;br&gt;
      HttpMethod.get =&amp;gt; await _get(context),&lt;br&gt;
      HttpMethod.post =&amp;gt; await _post(context),&lt;br&gt;
      _ =&amp;gt; Response(statusCode: HttpStatus.notFound)&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;Future _get(RequestContext context) async {&lt;br&gt;
    final todos = await context.read().fetchAll();&lt;br&gt;
    final json = [for (var todo in todos) todo.asMap];&lt;br&gt;
    return Response.json(body: json);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;Future _post(RequestContext context) async {&lt;br&gt;
    final json = await context.request.json() as Map;&lt;br&gt;
    final todo = await context.read().add(Todo.fromMap(json));&lt;br&gt;
    return Response.json(body: todo.asMap);&lt;br&gt;
  }&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
* Handlers for `/todos/[id]` in `routes/todos/[id].dart`
  ```dart


  Future&amp;lt;Response&amp;gt; onRequest(RequestContext context, String id) async {
    return switch (context.request.method) {
      HttpMethod.get =&amp;gt; await _get(context, id),
      HttpMethod.put =&amp;gt; await _put(context, id),
      HttpMethod.delete =&amp;gt; await _delete(context, id),
      _ =&amp;gt; Response(statusCode: HttpStatus.notFound)
    };
  }

  Future&amp;lt;Response&amp;gt; _get(RequestContext context, String id) async {
    final todo = await context.read&amp;lt;TodoRepository&amp;gt;().getById(id);

    return Response.json(body: todo.asMap);
  }

  Future&amp;lt;Response&amp;gt; _put(RequestContext context, String id) async {
    final json = await context.request.json() as Map&amp;lt;String, dynamic&amp;gt;;
    final todo = Todo.fromMap(json);
    await context.read&amp;lt;TodoRepository&amp;gt;().updateById(todo);
    return Response();
  }

  Future&amp;lt;Response&amp;gt; _delete(RequestContext context, String id) async {
    final json = await context.request.json() as Map&amp;lt;String, dynamic&amp;gt;;
    final id = json['id'] as String;
    await context.read&amp;lt;TodoRepository&amp;gt;().deleteById(id);
    return Response();
  }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each handler does the simple thing. Just call the repository methods and return the response. The repository is injected into the handler using the dependency injection with the middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Injection with the middleware
&lt;/h3&gt;

&lt;p&gt;Dart Frog supports the dependency injection natively. We can inject the repository into the handler using the middleware. The middlewares are also directory based. For example, &lt;code&gt;routes/todos/_middleware.dart&lt;/code&gt; is the middleware for the &lt;code&gt;routes/todos&lt;/code&gt; route. The &lt;code&gt;_middleware.dart&lt;/code&gt; file is a particular file name that represents the middleware for the directory.&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;InMemoryTodoRepository&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_todoData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;handler&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;provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;_todoData&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="n"&gt;InMemoryTodoRepository&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The code above injects the &lt;code&gt;InMemoryTodoRepository&lt;/code&gt; into the provider so the handler can access the repository. Thanks to the middleware with the dependency injection, we can easily use it from the handler as below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Repository with in-memory database
&lt;/h3&gt;

&lt;p&gt;I said it's a database, but it's just a simple List. It's not an actual database. It's just an example. The repository interface is in the shared package. The backend implements it as the in-memory database repository. The repository contains the List of Todo data model.&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;class&lt;/span&gt; &lt;span class="nc"&gt;InMemoryTodoRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;TodoRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// Local data&lt;/span&gt;
    &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="c1"&gt;/// Get List of [Todo]&lt;/span&gt;
    &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&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;todos&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;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@override&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;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstWhere&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&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;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// Add a new [todo] and return [Todo] with new id&lt;/span&gt;
    &lt;span class="nd"&gt;@override&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;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;newTodo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
      &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@override&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;deleteById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&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;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;id&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@override&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchAll&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@override&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;updateById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&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;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;t&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;As you can see, it has some CRUD methods for mutating the &lt;code&gt;List&amp;lt;Todo&amp;gt; data&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;The Frontend app is written in Flutter. It uses the Riverpod for state management and dependency injection. Same as the backend architecture, we store the implemented repository in the provider so the &lt;code&gt;todoListProvider&lt;/code&gt; can access the repository. The &lt;code&gt;todoListProvider&lt;/code&gt; is the riverpod provider that handles the business logic of the todo list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation and pages
&lt;/h3&gt;

&lt;p&gt;There are two folders for UI code: &lt;code&gt;presentation&lt;/code&gt; and &lt;code&gt;pages&lt;/code&gt;. The &lt;code&gt;presentation&lt;/code&gt; folder contains the code for widgets. The &lt;code&gt;pages&lt;/code&gt; folder contains the UI code for pages. The &lt;code&gt;pages/todo_page.dart&lt;/code&gt; is the main page that displays the todo list and form using the &lt;code&gt;TodoListView&lt;/code&gt; and &lt;code&gt;TodoTextField&lt;/code&gt; widgets in the &lt;code&gt;presentation&lt;/code&gt; folder. &lt;code&gt;main.dart&lt;/code&gt; is the entry point of the Flutter application that displays the &lt;code&gt;TodoPage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the body of the &lt;code&gt;TodoPage&lt;/code&gt;, it renders a column with the &lt;code&gt;TodoListView&lt;/code&gt; and &lt;code&gt;TodoTextField&lt;/code&gt; widgets.&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;const&lt;/span&gt; &lt;span class="nf"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;TodoTextField&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;SelectionArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;TodoListView&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;The &lt;code&gt;TodoTextFiled&lt;/code&gt; Widget is a simple text field that handles the input and submits the todo item. When the user submits the text field, it's calling todoListProvier's &lt;code&gt;add&lt;/code&gt; method.&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;class&lt;/span&gt; &lt;span class="nc"&gt;_TodoTextFieldState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerState&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TodoTextField&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;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&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;focusNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FocusNode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;hintText:&lt;/span&gt; &lt;span class="s"&gt;'Enter a new todo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;focusNode:&lt;/span&gt; &lt;span class="n"&gt;focusNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;onSubmitted:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoListProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;focusNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestFocus&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;TodoListView&lt;/code&gt; Widget is a simple list view that displays the todo list. It watches the todoListProvider and rebuilds the list view when the todo list is changed. When the user clicks the checkbox, it calls todoListProvier's &lt;code&gt;toggle&lt;/code&gt; method.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoListView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TodoListView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WidgetRef&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoListProvider&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
      &lt;span class="n"&gt;AsyncData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&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;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
          &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
          &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
          &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&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;TodoItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;todo:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;&lt;br&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="n"&gt;AsyncLoading&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;&lt;br&gt;
      &lt;span class="n"&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WidgetRef&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spaceBetween&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;br&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
        &lt;span class="n"&gt;Checkbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
          &lt;span class="nl"&gt;value:&lt;/span&gt; &lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
          &lt;span class="nl"&gt;onChanged:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
            &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoListProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Business logic with the Riverpod&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;todoListProvider&lt;/code&gt; is the riverpod's AsyncNotifier provider that handles the business logic of the todo list. It calls the &lt;code&gt;RemoteTodoRepository&lt;/code&gt;'s methods for communicating with the backend. When it's initialized, it calls the &lt;code&gt;fetchAll&lt;/code&gt; method of the &lt;code&gt;TodoRepository&lt;/code&gt; to fetch all the Todo items from the backend and cache them. The &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;toggle&lt;/code&gt; methods call the &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;updateById&lt;/code&gt; methods of the &lt;code&gt;TodoRepository&lt;/code&gt;. It updates the backend todo list and renews the local cache with the latest todo items in the provider.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nd"&gt;@riverpod&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoList&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;_$TodoList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;FutureOr&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&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;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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;br&gt;
    &lt;span class="n"&gt;state&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;AsyncValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;guard&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;br&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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;br&gt;
    &lt;span class="n"&gt;state&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;AsyncValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;guard&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;br&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;br&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;isDone:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDone&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;br&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todoRepositoryProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Rest API Client&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RemoteTodoRepository&lt;/code&gt; is the rest api client for communicating with the backend, which is implementing the &lt;code&gt;TodoRepository&lt;/code&gt; interface. It uses the &lt;code&gt;http&lt;/code&gt; package for making the HTTP request. It's calling the &lt;code&gt;http&lt;/code&gt; package's &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; methods for communicating with the backend.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RemoteTodoRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;TodoRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&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;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$baseURL&lt;/span&gt;&lt;span class="s"&gt;/todos"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Origin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;jsonEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asMap&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="k"&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;Todo&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="na"&amp;gt;fromMap&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;jsonDecode&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;json&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;));&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&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;deleteById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&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;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$baseURL&lt;/span&gt;&lt;span class="s"&gt;/todos/&lt;/span&gt;&lt;span class="si"&gt;$id&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Origin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fetchAll&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;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$baseURL&lt;/span&gt;&lt;span class="s"&gt;/todos"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Origin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="k"&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span class="p"&amp;gt;[&amp;lt;/span&amp;gt;&amp;lt;span class="k"&amp;gt;for&amp;lt;/span&amp;gt; &amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="kd"&amp;gt;final&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;todo&amp;lt;/span&amp;gt; &amp;lt;span class="k"&amp;gt;in&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;jsonDecode&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;json&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;))&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;Todo&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="na"&amp;gt;fromMap&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;todo&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)];&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&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;Todo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&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;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$baseURL&lt;/span&gt;&lt;span class="s"&gt;/todos/&lt;/span&gt;&lt;span class="si"&gt;$id&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Origin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="k"&amp;gt;return&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;Todo&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="na"&amp;gt;fromMap&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;jsonDecode&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;json&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;));&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&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;updateById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Todo&lt;/span&gt; &lt;span class="n"&gt;todo&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;br&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$baseURL&lt;/span&gt;&lt;span class="s"&gt;/todos/&lt;/span&gt;&lt;span class="si"&gt;${todo.id}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Origin'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;jsonEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asMap&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;We've built a full-stack application with Flutter and Dart. We used Flutter for the client and Dart Frog for the server. Flutter displayed the UI, and Dart Frog handled the business logic and data storage. We used the mono-repo structure for the project. We created three packages: &lt;code&gt;backend&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt;, and &lt;code&gt;shared&lt;/code&gt;. &lt;code&gt;shared&lt;/code&gt; package contained all the code that is shared between the client and server. &lt;code&gt;backend&lt;/code&gt; package contained all the code for the server(Dart Frog) and &lt;code&gt;frontend&lt;/code&gt; package contained all the code for the client(Flutter).&lt;/p&gt;

&lt;p&gt;We would need to run performance benchmarks to use dart as the backend language before we decided to build a production application, but the development experience was great. I liked the basic tooling of the dart_frog. It's simple and easy to use, like Flutter. I also liked the dependency injection with the middleware. I could build the full-stack application with the Dart Frog without spending too much time learning the framework.&lt;/p&gt;

&lt;p&gt;The complete code example is available at &lt;a href="https://github.com/leehack/fullstack_dart_todo" rel="noopener noreferrer"&gt;https://github.com/leehack/fullstack_dart_todo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>fullstack</category>
      <category>dart</category>
      <category>flutter</category>
      <category>monorepo</category>
    </item>
    <item>
      <title>How to use Firebase Emulator suite for Flutter</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Thu, 29 Jun 2023 14:38:25 +0000</pubDate>
      <link>https://dev.to/leehack/how-to-use-firebase-emulator-suite-for-flutter-1i7f</link>
      <guid>https://dev.to/leehack/how-to-use-firebase-emulator-suite-for-flutter-1i7f</guid>
      <description>&lt;p&gt;The Firebase provides an emulator suite for the local dev environment. The suite allows us to run the Firebase on the local machine. So, we can locally use Cloud Firestore, Realtime Database, Cloud Storage for Firebase, Authentication, Firebase Hosting, Cloud Functions (beta), Pub/Sub (beta), and Firebase Extensions (beta) for the app we are working on.&lt;/p&gt;

&lt;p&gt;More detail can be found at &lt;a href="https://firebase.google.com/docs/emulator-suite" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/emulator-suite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fzfd3164gxs1adx5b16rb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fzfd3164gxs1adx5b16rb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-Requisite
&lt;/h2&gt;

&lt;p&gt;The Flutter project is initialized for the Firebase: &lt;a href="https://firebase.google.com/docs/flutter/setup" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/flutter/setup&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the Emulator Suite
&lt;/h2&gt;

&lt;p&gt;Setup the Emulator Suite with this command under the project directory:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

firebase init emulators


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Start the Emulator Suite
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

firebase emulators:start


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Run the Flutter app using the Emulators
&lt;/h2&gt;

&lt;p&gt;Add this lines of code in the &lt;code&gt;main()&lt;/code&gt; in &lt;code&gt;main.dart&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&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="n"&gt;WidgetsFlutterBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Initialize Firebase&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Firebase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;DefaultFirebaseOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentPlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Use Firebase Emulators&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;kDebugMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Only for debug mode.&lt;/span&gt;
    &lt;span class="k"&gt;try&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;emulatorHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultTargetPlatform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TargetPlatform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;android&lt;/span&gt;
          &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"10.0.2.2"&lt;/span&gt;
          &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;FirebaseStorage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;useStorageEmulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emulatorHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9199&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;FirebaseFirestore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;useFirestoreEmulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emulatorHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ignore: avoid_print&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="c1"&gt;// Start Flutter Application&lt;/span&gt;
  &lt;span class="n"&gt;runApp&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;MyApp&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 code example makes the app use FireStorage and Firestore Emulators in debug mode. It also provides Web UI(&lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;) for easy inspection.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>firebase</category>
      <category>dart</category>
    </item>
    <item>
      <title>How to screenshot a widget in the flutter</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Wed, 23 Nov 2022 00:32:06 +0000</pubDate>
      <link>https://dev.to/leehack/how-to-screenshot-a-widget-in-the-flutter-1h5l</link>
      <guid>https://dev.to/leehack/how-to-screenshot-a-widget-in-the-flutter-1h5l</guid>
      <description>&lt;h3&gt;
  
  
  Define a global key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;GlobalKey&lt;/span&gt; &lt;span class="n"&gt;previewContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GlobalKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrap the screenshot area with RepaintBoundary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;FittedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;fit:&lt;/span&gt; &lt;span class="n"&gt;BoxFit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;RepaintBoundary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;previewContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// The widget to capture&lt;/span&gt;
            &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Screenshot for the widget
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&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;Uint8List&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_capture&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="n"&gt;RenderRepaintBoundary&lt;/span&gt; &lt;span class="n"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;previewContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentContext&lt;/span&gt;
        &lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;findRenderObject&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;RenderRepaintBoundary&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;image&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;boundary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;pixelRatio:&lt;/span&gt; &lt;span class="mi"&gt;2&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;byteData&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;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toByteData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;ImageByteFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;png&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;byteData&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asUint8List&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;



</description>
      <category>flutter</category>
      <category>instagram</category>
      <category>widget</category>
      <category>story</category>
    </item>
    <item>
      <title>Journey to the clean architecture for my flutter app</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Mon, 31 Oct 2022 19:15:31 +0000</pubDate>
      <link>https://dev.to/leehack/journey-to-the-clean-architecture-for-my-flutter-app-138n</link>
      <guid>https://dev.to/leehack/journey-to-the-clean-architecture-for-my-flutter-app-138n</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;When I started my flutter project two years ago, Android Jetpack architecture inspired me a lot. DataBinding between ViewModel and View using LiveData that comes from the data layer passing through the repository and so on, as I mentioned in another &lt;a href="https://dev.to/leehack/journey-to-the-riverpod-for-my-flutter-app-2hk0"&gt;posting&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fe1sCpLq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fe1sCpLq.png" alt="MVVM with data layer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(I cannot find this diagram anymore from the official google document. They added the domain layer as an optional recommendation: &lt;a href="https://developer.android.com/topic/architecture" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;My project folder structure was something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- lib
  - repositories
    - a_repository.dart
    - b_repository.dart
  - models
    - a_model.dart
    - b_model.dart
  - services
    - a_service.dart
    - b_service.dart
  - ui
    - pages
      - pageA
        - a_page.dart
        - a_viewmodel.dart
      - pageB
        - b_page.dart
        - b_viewmodel.dart
    - widgets
      - a_widget.dart
      - b_widget.dart
  - main.dart
  - DI.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DI.dart&lt;/code&gt; wasn't truly doing the dependency injection. Still, I was initializing all global dependencies as singletons to inject them into the page-scoped ViewModels when initializing the page.&lt;/p&gt;

&lt;p&gt;The problem with this approach was that the ViewModel held too much business logic. If I needed to use similar logic for another page, I had to reimplement the logic in the ViewModel. Eventually, I started adding ViewModels in a shared folder and reusing them directly from the widgets, creating another instance of the ViewModel. It reduced the code duplication, but it made it look messy. Whenever I tried to add a new feature, it was confusing where I should put the new codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FRPFDkAy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FRPFDkAy.png" alt="From cleancoder blog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After some research, I decided to apply the clean architecture I've used for a golang backend project. The clean architecture was quite famous for the flutter project, too, since it's a recommended architecture by &lt;a href="https://bloclibrary.dev/" rel="noopener noreferrer"&gt;BLoC&lt;/a&gt;, a popular state management library in dart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer-First structure
&lt;/h3&gt;

&lt;p&gt;I started to restructure the directory based on the layer. (At this moment, &lt;a href="https://dev.to/leehack/journey-to-the-riverpod-for-my-flutter-app-2hk0"&gt;I also decided to use riverpod instead of provider&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- lib
  - data
    - remote
      - repositories
        - a_repository_impl.dart
        - b_repository_impl.dart
      - models
        - a_model.dart
        - b_model.dart
      - services
        - a_service.dart
        - b_service.dart
      - data_provider.dart
  - domain
    - entities
      - a_entity.dart
      - b_entity.dart
    - repositories
      - a_repository.dart
      - b_repository.dart
    - usecases
      - featureA
        - get.dart
        - get_many.dart
      - featureB
        - get.dart
        - get_by_filter.dart
    - providers
      - feature_a_usecase_provider.dart
      - feature_b_usecase_provider.dart
  - presentation
    - states
      - a_state_provider.dart
      - b_state_provider.dart
    - pages
      - pageA
        - a_page.dart
      - pageB
        - b_page.dart
    - widgets
      - a_widget.dart
      - b_widget.dart
  - main.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repositories in the &lt;strong&gt;domain&lt;/strong&gt; layer are &lt;strong&gt;abstract&lt;/strong&gt; classes that interface between the &lt;strong&gt;domain&lt;/strong&gt; and the &lt;strong&gt;data&lt;/strong&gt; layer. The &lt;strong&gt;data_provider.dart&lt;/strong&gt; initialize the service and repository providers. The providers in the domain layer are initializing the usecases using repository providers defined in &lt;strong&gt;data_provider.dart&lt;/strong&gt;. The &lt;strong&gt;states&lt;/strong&gt; in the &lt;strong&gt;presentation&lt;/strong&gt; layer only access &lt;strong&gt;entities&lt;/strong&gt; and &lt;strong&gt;usecase&lt;/strong&gt; providers in the &lt;strong&gt;domain&lt;/strong&gt; layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model is a service-specific model that holds entity getter(mapper)&lt;/strong&gt;&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="c1"&gt;// a_model.dart&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ModelA&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&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;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;EntityA&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;entity&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;Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&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;&lt;strong&gt;The service can locally or remotely access the raw data source&lt;/strong&gt;&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="c1"&gt;// a_service.dart&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ModelA&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceAImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;ServiceA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ModelA&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ModelA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;"test"&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;&lt;strong&gt;The implementation of the repository that accesses the service&lt;/strong&gt;&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="c1"&gt;// a_repository_impl.dart&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepositoryAImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;RepositoryA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RepositoryAImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&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;ServiceA&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;EntityA&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entity&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;&lt;strong&gt;Initialize services and repositories(Services are private)&lt;/strong&gt;&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="c1"&gt;// data_provider.dart&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_serviceAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;ServiceAImpl&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;repositoryAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepositoryA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&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;RepositoryAImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_serviceAProvider&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;&lt;strong&gt;Domain entity that application uses&lt;/strong&gt;&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="c1"&gt;// a_entity.dart&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntityA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EntityA&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&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;name&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;&lt;strong&gt;Interfacing between domain and data layer&lt;/strong&gt;&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="c1"&gt;// a_repository.dart&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepositoryA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EntityA&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&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;&lt;strong&gt;The usecase can access the repository&lt;/strong&gt;&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="c1"&gt;// featureA/get.dart&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EntityA&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetAImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;GetA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetAImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repo&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;RepositoryA&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;EntityA&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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;&lt;strong&gt;The states in the presentation layer access the usecase through the provider&lt;/strong&gt;&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="c1"&gt;// feature_a_usecase_provider.dart&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;getAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GetA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&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;GetImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repositoryAProvider&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;&lt;strong&gt;States are globally shared and accessible from widgets&lt;/strong&gt;&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="c1"&gt;// a_state_provider.dart&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;stateAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StateNotifierProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EntityA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;StateANotifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EntityA&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StateANotifier&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StateNotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EntityA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;StateANotifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ref&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;Ref&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getAProvider&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;id&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;&lt;strong&gt;Widget accesses the states&lt;/strong&gt;&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="c1"&gt;// a_widget.dart&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WidgetA&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WidgetRef&lt;/span&gt; &lt;span class="n"&gt;ref&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;stateA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;InkWell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&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;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${stateA.name}&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;After the massive refactoring of the architecture (Even though it looks like there are a lot of boilerplate codes), I was pretty satisfied. I don't need to think twice about where to put which logic. Every business logic goes to the usecase and entity. If the view needs to update the state, I create the &lt;strong&gt;StateNotifierProvider&lt;/strong&gt;. The Widgets only focus on the viewing part. The separation gives better testability and lets the codes keep the single responsibility rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature-First structure
&lt;/h3&gt;

&lt;p&gt;After the refactoring, the only problem I had was that it was not easy to navigate the source code. When implementing a feature, I wished all related codes were grouped. Since the codes are split based on the layer, It was pretty easy to lose my focus.&lt;br&gt;
So, I decided to do another refactoring for a feature-first structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- lib
  - features
    - featureA
      - domain
        - entity
          - a_entity.dart
        - repository
          - a_repository.dart
        - usecases
          - get.dart
          - get_many.dart
      - data
        - repositories
          - a_repository_impl.dart
        - models
          - a_model.dart
        - services
          - a_service.dart
        - data_provider.dart
      - providers
        - feature_a_usecase_provider.dart
    - featureB
      - domain
        - entity
          - b_entity.dart
        - repository
          - b_repository.dart
        - usecases
          - get.dart
          - get_many.dart
      - data
        - repositories
          - b_repository_impl.dart
        - models
          - b_model.dart
        - services
          - b_service.dart
        - data_provider.dart
      - providers
        - feature_b_usecase_provider.dart
  - presentation
    - states
      - a_state_provider.dart
      - b_state_provider.dart
    - pages
      - pageA
        - a_page.dart
      - pageB
        - b_page.dart
    - widgets
      - a_widget.dart
      - b_widget.dart
  - main.dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the code-wise, nothing changed. Some might wonder why I didn't put the presentation in the feature directory. I still don't have a conclusion on this, but I thought the UI should use different domains, and I didn't want to cross-reference between features. I didn't specify above, but I put the shared codes in the &lt;strong&gt;core&lt;/strong&gt; feature. It's the only exception that other features can reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I don't have any good recommendations for a small project or team if the clean architecture is a good choice or not since it requires some boilerplate codes. But It gives us a good separation between layers and domains, making the code more testable. It can take time to write the pattern every time adding a new feature, but the time can be easily compensated by focusing on implementing a single domain feature. There's no perfect architecture or solution. One day, we need to refactor our code base. A library we've been using probably doesn't fit our needs anymore or introduces security vulnerabilities. The well-structured architecture allows quickly switching a library or refactoring a part of your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codewithandrea.com/articles/flutter-project-structure/" rel="noopener noreferrer"&gt;https://codewithandrea.com/articles/flutter-project-structure/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/" rel="noopener noreferrer"&gt;https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>architecture</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>Journey to the riverpod for my flutter app.</title>
      <dc:creator>Jhin Lee</dc:creator>
      <pubDate>Mon, 31 Oct 2022 01:59:38 +0000</pubDate>
      <link>https://dev.to/leehack/journey-to-the-riverpod-for-my-flutter-app-2hk0</link>
      <guid>https://dev.to/leehack/journey-to-the-riverpod-for-my-flutter-app-2hk0</guid>
      <description>&lt;p&gt;In this article, I'd like to talk about how I ended up using Riverpod as a state management system for my flutter app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provider with MVVM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_it&lt;/code&gt; for global dependencies&lt;/li&gt;
&lt;li&gt;Riverpod&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Provider with MVVM
&lt;/h2&gt;

&lt;p&gt;When I started the flutter project as a newbie two years ago, I wanted to build something similar to the MVVM pattern I used to use for Android application development. After some research, I found &lt;a href="https://pub.dev/packages/provider"&gt;Provider&lt;/a&gt; the most straightforward option. (I wasn't even thinking of state management at that time)&lt;/p&gt;

&lt;p&gt;So, I was doing something like this at the entry point of the app:&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;serviceA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceA&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;serviceB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;MultiProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;providers:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;RepoA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceA&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoB&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;RepoB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceA&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoC&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;RepoC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceB&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&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;I've been initializing the &lt;code&gt;services&lt;/code&gt; and &lt;code&gt;repositories&lt;/code&gt; at the application launch time and adding them into the Provider using the &lt;code&gt;MultiProvider&lt;/code&gt;. It allowed the &lt;code&gt;ViewModels&lt;/code&gt; of each page to access the repositories. (Yes, one &lt;code&gt;view&lt;/code&gt;, one &lt;code&gt;ViewModel&lt;/code&gt;)&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;class&lt;/span&gt; &lt;span class="nc"&gt;ViewModelA&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ChangeNotifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ViewModelA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;init&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;RepoA&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&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;init&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="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;notifiyListeners&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="c1"&gt;// Wrap the page with ChangeNotifierProvider&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PageA&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ChangeNotifierProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ViewModelA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;listen:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;PageView&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;I could access the' ViewModel' in any widgets used in the &lt;code&gt;PageView&lt;/code&gt; using the &lt;code&gt;Selector&lt;/code&gt;.&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;Selector&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ViewModelA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;selector:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&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;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'title: &lt;/span&gt;&lt;span class="si"&gt;${data}&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;It was annoying to write the boilerplate codes of the &lt;code&gt;Selector&lt;/code&gt; every time I needed to use variables in the ViewModel, but I helped to reduce unnecessary rendering. So far, It's not bad.&lt;/p&gt;

&lt;p&gt;After growing the code bases with many repositories and services, I realized that every time I add more repositories in the global provider scope with the &lt;code&gt;MultiProvider&lt;/code&gt;, it creates the cascading tree in the widget hierarchy. It made debugging super tricky with the Widget Inspector tool.&lt;/p&gt;

&lt;p&gt;One day, I decided to remove &lt;code&gt;MultiProvider&lt;/code&gt; and convert all global dependencies as singletons. Technically they were already singletons but just living in the Provider scope anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  get_it for global dependencies
&lt;/h2&gt;

&lt;p&gt;After some research, I found the &lt;a href="https://pub.dev/packages/get_it"&gt;get_it&lt;/a&gt; looked promising.&lt;br&gt;
I replaced &lt;code&gt;MultiProvider&lt;/code&gt; with getIt singletons at the application start:&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;serviceA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceA&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;serviceB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ServiceB&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;getIt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceA&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;getIt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoB&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;RepoB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceA&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;getIt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoC&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;RepoC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;service:&lt;/span&gt;&lt;span class="n"&gt;serviceB&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the View and ViewModel like this:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ViewModelA&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ChangeNotifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This allows passing mocked repository to the ViewModel for testing purposes&lt;/span&gt;
  &lt;span class="n"&gt;ViewModelA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;GetIt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;init&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;RepoA&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;title&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;init&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="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;notifiyListeners&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;class&lt;/span&gt; &lt;span class="nc"&gt;PageA&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ChangeNotifierProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;create:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ViewModelA&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;PageView&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;Now, I don't see the cascading tree in the widget inspector, and much more comfortable debugging the widgets. I also liked it because the widget doesn't need to know what dependencies the ViewModel needs. Thanks to the GetIt can give it to me without &lt;code&gt;context&lt;/code&gt;. But I still have a slow application start problem.&lt;/p&gt;

&lt;p&gt;A few months later, I wanted to tackle the start-up time issue and realized I might have a few options.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use lazy singleton with GetIt.&lt;/li&gt;
&lt;li&gt;Go for another approach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I could have converted the global dependencies to lazy singleton with GetIt so that it doesn't need to initialize all dependencies at the application start. However, It would still keep in the memory until the app completely shut down. I wanted something that could automatically initiate and de-initiate based on the need.&lt;br&gt;
So another round of research started!&lt;/p&gt;
&lt;h2&gt;
  
  
  Riverpod
&lt;/h2&gt;

&lt;p&gt;In the meantime, I also was working on a react project that uses the &lt;a href="https://tanstack.com/query"&gt;React Query&lt;/a&gt;, and even though it's not a client-side state management library, I found it a simple and nice way of managing states. I wanted to have something similar but for flutter. Finally, the &lt;a href="https://riverpod.dev/"&gt;Riverpod&lt;/a&gt; caught my eye. The developer of the Provider developed Riverpod as a successor of the Provider. (Later, I found &lt;a href="https://recoiljs.org/"&gt;Recoil&lt;/a&gt; is the one I've been looking for in the React app for client state management, and it has a lot of similarities with Riverpod)&lt;/p&gt;

&lt;p&gt;As a first step, I converted all singleton services and repositories into Riverpod providers. I also defined &lt;code&gt;abstract classes&lt;/code&gt; for better testability.&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;ServiceA&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceAImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;ServiceA&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceB&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceBImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;ServiceB&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoA&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoAImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;RepoA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;RepoAImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&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;ServiceA&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getMany&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMany&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;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoB&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoBImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;RepoB&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;RepoBImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&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;ServiceA&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoC&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepoCImpl&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;RepoC&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;RepoCImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&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;ServiceB&lt;/span&gt; &lt;span class="n"&gt;service&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;serviceAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;ServiceAImpl&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;serviceBProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ServiceB&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&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;ServiceBImpl&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;repoAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&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;RepoAImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceAProvider&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;repoBProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoB&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&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;RepoBImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceAProvider&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;repoCProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RepoC&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&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;RepoCImpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceBProvider&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also started getting rid of the ViewModels. Since I can now define a state as a provider that is accessible from any widgets, the ViewModel was too heavy as a state. This way, I was able to write more reusable codes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create state providers&lt;/strong&gt;&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="c1"&gt;// Simple future state provider&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;stateAProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FutureProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EntityA&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repoAProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// State provider with notifier that contains methods.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;stateBProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StateNotifierProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EntityB&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;StateBNotifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EntityB&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repoAProvider&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StateBNotifier&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StateNotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ThemeMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;StateBNotifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repo&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;RepoA&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMany&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;I can access the providers from widgets.&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;class&lt;/span&gt; &lt;span class="nc"&gt;WidgetA&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WidgetRef&lt;/span&gt; &lt;span class="n"&gt;ref&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;stateB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateBProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;InkWell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&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;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateBProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${stateB.name}&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;With the Riverpod, I could make the state lighter and more reusable. I often create a container widget that holds the state that wraps the pure widgets. This way, the design widget is reusable as a design widget without business context. Providers and widgets are all unit-testable by simply mocking the repository or other dependency of the Provider.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>architecture</category>
      <category>riverpod</category>
      <category>dart</category>
    </item>
  </channel>
</rss>
