<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Abijah Kajabika]]></title><description><![CDATA[Abijah Kajabika]]></description><link>https://blog.abijah.me</link><generator>RSS for Node</generator><lastBuildDate>Sun, 26 Apr 2026 21:55:00 GMT</lastBuildDate><atom:link href="https://blog.abijah.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Declarative vs Imperative APIs in JavaScript (and Why Chainable Code Feels So Different)]]></title><description><![CDATA[When people talk about “declarative” APIs, they often mean something vague like cleaner, functional, or React-style. But the real difference between declarative and imperative (non-declarative) APIs is much simpler:

Imperative code tells the compute...]]></description><link>https://blog.abijah.me/declarative-vs-imperative-apis-in-javascript-and-why-chainable-code-feels-so-different</link><guid isPermaLink="true">https://blog.abijah.me/declarative-vs-imperative-apis-in-javascript-and-why-chainable-code-feels-so-different</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[programming language theory]]></category><category><![CDATA[Declarative vs. Imperative Programming]]></category><dc:creator><![CDATA[Abijah Kajabika]]></dc:creator><pubDate>Wed, 28 Jan 2026 19:04:42 GMT</pubDate><content:encoded><![CDATA[<p>When people talk about “declarative” APIs, they often mean something vague like <em>cleaner</em>, <em>functional</em>, or <em>React-style</em>. But the real difference between declarative and imperative (non-declarative) APIs is much simpler:</p>
<blockquote>
<p>Imperative code tells the computer <strong>how</strong> to do something.<br />Declarative code tells the computer <strong>what</strong> you want.</p>
</blockquote>
<p>This distinction matters a lot when designing or choosing APIs, especially in JavaScript/Typescript, where chainable calls, callbacks, and fluent interfaces are everywhere.</p>
<p>Let’s break it down.</p>
<hr />
<h2 id="heading-the-core-idea-how-vs-what">The core idea: how vs what</h2>
<h3 id="heading-imperative-non-declarative">Imperative (non-declarative)</h3>
<p>You control every step of the process:</p>
<pre><code class="lang-js"><span class="hljs-keyword">let</span> result = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; users.length; i++) {
  <span class="hljs-keyword">if</span> (users[i].active) {
    result.push(users[i].name.toUpperCase());
  }
}
</code></pre>
<p>You explicitly manage:</p>
<ul>
<li><p>loops</p>
</li>
<li><p>conditions</p>
</li>
<li><p>intermediate state</p>
</li>
<li><p>execution order</p>
</li>
</ul>
<p>You’re describing the <em>procedure</em>.</p>
<hr />
<h3 id="heading-declarative">Declarative</h3>
<p>You describe the desired result:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> result = users
  .filter(<span class="hljs-function"><span class="hljs-params">u</span> =&gt;</span> u.active)
  .map(<span class="hljs-function"><span class="hljs-params">u</span> =&gt;</span> u.name.toUpperCase());
</code></pre>
<p>Now:</p>
<ul>
<li><p>no explicit loop management</p>
</li>
<li><p>no mutation</p>
</li>
<li><p>no temporary variables</p>
</li>
<li><p>no control flow</p>
</li>
</ul>
<p>You express <em>intent</em>, and the runtime figures out execution.</p>
<hr />
<h2 id="heading-apis-can-be-declarative-too">APIs can be declarative too</h2>
<p>This idea applies not only to syntax, but to API design.</p>
<h3 id="heading-imperative-api">Imperative API</h3>
<pre><code class="lang-js">db.connect();
db.selectTable(<span class="hljs-string">"users"</span>);
db.addFilter(<span class="hljs-string">"age &gt; 18"</span>);
db.sort(<span class="hljs-string">"name"</span>);
<span class="hljs-keyword">const</span> result = db.execute();
</code></pre>
<p>You orchestrate every step.</p>
<hr />
<h3 id="heading-declarative-api">Declarative API</h3>
<pre><code class="lang-js">db.query({
  <span class="hljs-attr">table</span>: <span class="hljs-string">"users"</span>,
  <span class="hljs-attr">where</span>: <span class="hljs-string">"age &gt; 18"</span>,
  <span class="hljs-attr">orderBy</span>: <span class="hljs-string">"name"</span>
});
</code></pre>
<p>You describe the outcome. The system decides how to achieve it.</p>
<hr />
<h2 id="heading-what-about-chainable-apis">What about chainable APIs?</h2>
<p>Chaining <em>does not automatically</em> mean declarative. It depends on what the chain represents.</p>
<h3 id="heading-declarative-chaining">Declarative chaining</h3>
<pre><code class="lang-js">users
  .filter(<span class="hljs-function"><span class="hljs-params">u</span> =&gt;</span> u.active)
  .map(<span class="hljs-function"><span class="hljs-params">u</span> =&gt;</span> u.name)
  .sort();
</code></pre>
<p>This builds a <strong>transformation pipeline</strong>:</p>
<ul>
<li><p>each call describes a rule</p>
</li>
<li><p>nothing mutates external state</p>
</li>
<li><p>no side effects</p>
</li>
<li><p>execution is abstracted</p>
</li>
</ul>
<p>You’re defining <em>what should happen to the data</em>.</p>
<hr />
<h3 id="heading-imperative-chaining">Imperative chaining</h3>
<pre><code class="lang-js">request
  .open()
  .setHeader(<span class="hljs-string">"Authorization"</span>, token)
  .send()
  .onSuccess(handleSuccess);
</code></pre>
<p>Here:</p>
<ul>
<li><p>each call performs an action immediately</p>
</li>
<li><p>order matters operationally</p>
</li>
<li><p>side effects occur</p>
</li>
<li><p>you are commanding steps</p>
</li>
</ul>
<p>It’s still imperative even though it looks fluent.</p>
<hr />
<h2 id="heading-callbacks-dont-decide-declarativity">Callbacks don’t decide “declarativity”</h2>
<p>Callbacks themselves are neutral. What matters is <em>who controls execution</em>.</p>
<h3 id="heading-imperative-callback">Imperative callback</h3>
<pre><code class="lang-js">fs.readFile(<span class="hljs-string">"file.txt"</span>, <span class="hljs-function">(<span class="hljs-params">err, data</span>) =&gt;</span> {
  process(data);
});
</code></pre>
<p>You start the action and provide instructions.</p>
<hr />
<h3 id="heading-declarative-callback-registration">Declarative callback registration</h3>
<pre><code class="lang-js">router.get(<span class="hljs-string">"/users"</span>, {
  <span class="hljs-attr">auth</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">handler</span>: <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> { ... }
});
</code></pre>
<p>You’re declaring behavior, not triggering execution.</p>
<hr />
<h2 id="heading-immediate-execution-vs-building-a-description">Immediate execution vs building a description</h2>
<p>A useful mental model:</p>
<blockquote>
<p>If method calls execute immediately → more imperative<br />If method calls build a description to run later → more declarative</p>
</blockquote>
<p>Example:</p>
<pre><code class="lang-js">anim.move(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>);
anim.rotate(<span class="hljs-number">45</span>);
anim.scale(<span class="hljs-number">2</span>);
anim.play();
</code></pre>
<p>Imperative: actions happen now.</p>
<p>Versus:</p>
<pre><code class="lang-js">anim
  .move(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>)
  .rotate(<span class="hljs-number">45</span>)
  .scale(<span class="hljs-number">2</span>)
  .build();
</code></pre>
<p>Declarative: steps are recorded, not executed yet.</p>
<hr />
<h2 id="heading-real-world-javascript-examples">Real-world JavaScript examples</h2>
<h3 id="heading-mostly-declarative">Mostly declarative</h3>
<ul>
<li><p>React JSX</p>
</li>
<li><p>SQL query builders (Prisma, Knex)</p>
</li>
<li><p>CSS</p>
</li>
<li><p>GraphQL queries</p>
</li>
<li><p>Array methods (<code>map</code>, <code>filter</code>, <code>reduce</code>)</p>
</li>
<li><p>RxJS pipelines</p>
</li>
<li><p>Lodash chaining</p>
</li>
</ul>
<h3 id="heading-mostly-imperative">Mostly imperative</h3>
<ul>
<li><p>DOM manipulation</p>
</li>
<li><p>Canvas drawing</p>
</li>
<li><p>Node.js file system APIs</p>
</li>
<li><p>Fetch with manual control flow</p>
</li>
<li><p>jQuery classic chaining</p>
</li>
</ul>
<hr />
<h2 id="heading-a-quick-checklist">A quick checklist</h2>
<p>Ask yourself:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Question</td><td>Declarative</td><td>Imperative</td></tr>
</thead>
<tbody>
<tr>
<td>Do I manage loops?</td><td>❌</td><td>✅</td></tr>
<tr>
<td>Do I manage state?</td><td>❌</td><td>✅</td></tr>
<tr>
<td>Do I describe intent?</td><td>✅</td><td>❌</td></tr>
<tr>
<td>Is execution hidden?</td><td>✅</td><td>❌</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-why-this-matters">Why this matters</h2>
<p>Declarative APIs:</p>
<ul>
<li><p>are easier to reason about</p>
</li>
<li><p>reduce bugs from state management</p>
</li>
<li><p>compose better</p>
</li>
<li><p>are easier to optimize internally</p>
</li>
<li><p>are more readable at scale</p>
</li>
</ul>
<p>Imperative APIs:</p>
<ul>
<li><p>offer fine control</p>
</li>
<li><p>are sometimes necessary for performance</p>
</li>
<li><p>fit low-level tasks</p>
</li>
</ul>
<p>Most production systems combine both, but understanding the difference helps you design cleaner APIs and recognize why some code “feels” simpler.</p>
<hr />
<h2 id="heading-final-takeaway">Final takeaway</h2>
<blockquote>
<p>Declarative: <em>“Here is what I want.”</em><br />Imperative: <em>“Here is how to do it.”</em></p>
</blockquote>
<p>Once you start seeing this distinction, you’ll notice it everywhere, from React components to database queries to method chains.</p>
]]></content:encoded></item><item><title><![CDATA[Offline AI with React Native]]></title><description><![CDATA[In a world where internet connectivity is often unreliable (especially while on a plane), and privacy concerns are growing, I set out to build an AI-powered study assistant that works entirely offline. The goal was to explore what can be done with av...]]></description><link>https://blog.abijah.me/offline-ai-with-react-native</link><guid isPermaLink="true">https://blog.abijah.me/offline-ai-with-react-native</guid><category><![CDATA[ExecuTorch]]></category><category><![CDATA[React Native]]></category><category><![CDATA[llm]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[AI]]></category><category><![CDATA[pdf]]></category><category><![CDATA[#StudyTools]]></category><category><![CDATA[data extraction]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[React]]></category><category><![CDATA[State Management ]]></category><category><![CDATA[zustand]]></category><category><![CDATA[React-native MMKV]]></category><dc:creator><![CDATA[Abijah Kajabika]]></dc:creator><pubDate>Sun, 19 Oct 2025 03:51:58 GMT</pubDate><content:encoded><![CDATA[<p>In a world where internet connectivity is often unreliable (especially while on a plane), and privacy concerns are growing, I set out to build an AI-powered study assistant that works entirely offline. The goal was to explore what can be done with available tools: create a React Native app that could parse PDFs, extract meaningful content, and provide intelligent chat capabilities - all without requiring an internet connection.</p>
<h2 id="heading-the-user-experience">The User Experience</h2>
<p>I wanted the app to provide a seamless experience in these ways:</p>
<ol>
<li><p><strong>Upload PDF</strong>: Users can select PDFs from their device</p>
</li>
<li><p><strong>Automatic Processing</strong>: The app extracts text and creates chunks automatically</p>
</li>
<li><p><strong>Study Materials</strong>: Generates summaries, quizzes, flashcards, and mind maps</p>
</li>
<li><p><strong>AI Chat</strong>: Two chat modes - general AI assistant and document-specific tutor</p>
</li>
<li><p><strong>Offline Operation</strong>: Everything works without internet connectivity</p>
</li>
</ol>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>The LearnPDF app follows a simple architecture that combines several currently available technologies:</p>
<ul>
<li><p><strong>React Native/Expo</strong> for cross-platform mobile development (And why not use JS for everything?)</p>
</li>
<li><p><strong>React Native ExecuTorch</strong> for on-device AI model execution (running <a target="_blank" href="https://huggingface.co/software-mansion/react-native-executorch-llama-3.2"><code>Llama 3.2 1B</code></a> model)</p>
</li>
<li><p><strong>MMKV</strong> for high-performance local storage</p>
</li>
<li><p><strong>Zustand</strong> with Immer for state management</p>
</li>
<li><p><strong>Web based PDF parsing</strong> for text extraction</p>
</li>
<li><p><strong>RAG (Retrieval-Augmented Generation)</strong> for context-aware responses (using <a target="_blank" href="https://huggingface.co/software-mansion/react-native-executorch-all-MiniLM-L6-v2"><code>All-MiniLM-L6-v2</code></a> model for embeddings)</p>
</li>
</ul>
<h2 id="heading-the-journey-from-parsing-to-intelligence">The Journey: From Parsing to Intelligence</h2>
<h3 id="heading-1-pdf-processing-pipeline">1. PDF Processing Pipeline</h3>
<p>The foundation of the app lies in its ability to extract and process PDF content locally. I implemented a custom PDF processing system, this in the end turned out to be the most challenging part because I couldn’t use PDF.js or any other tool based on it in a react native environment (no support for web workers) and I initially didn’t want to use web views :</p>
<h4 id="heading-custom-pdf-parser-pdfextractorts">Custom PDF Parser (<code>PDFExtractor.ts</code>)</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PDFTextExtractor {
  <span class="hljs-keyword">private</span> buffer: <span class="hljs-built_in">Uint8Array</span>;
  <span class="hljs-keyword">private</span> objects: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">number</span>, PDFObject&gt; = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
  <span class="hljs-keyword">private</span> pages: PDFPage[] = [];

  <span class="hljs-keyword">async</span> extractText(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">string</span>&gt; {
    <span class="hljs-comment">// the would find cross-reference table to know the address of each object</span>
    <span class="hljs-built_in">this</span>.parseObjects();
    <span class="hljs-built_in">this</span>.findPages();
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.extractTextFromPages();
  }
}
</code></pre>
<p>So I set out to learn how to parse pdf and extract texts from it manually.<br />This first custom parser handles:</p>
<ul>
<li><p>PDF object parsing and decompression using <code>fflate</code></p>
</li>
<li><p>Stream extraction and text command parsing</p>
</li>
<li><p>Page-by-page text extraction</p>
</li>
<li><p>React Native-compatible string conversion</p>
</li>
</ul>
<p>This method worked for simple PDFs with support for limited encodings, but performed poorly on most of my PDFs, so I had to accept that the goal is not to rewrite PDF.js to run on react native without web views but to extract text and such I painfully forced myself to integrate a web view in the project.</p>
<h4 id="heading-webview-based-processing-pdfwebviewtsx">WebView-Based Processing (<code>PDFWebView.tsx</code>)</h4>
<p>So I set up the web view (using <code>react-native-webview</code>) in which I could run PDF.js and it worked amazingly well even for more complex PDFs:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> PDFWebView: React.FC&lt;PDFWebViewProps&gt; = <span class="hljs-function">(<span class="hljs-params">{
  fileUri,
  fileName,
  onTextExtracted,
}</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> loadPDFInWebView = useCallback(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> base64Data = <span class="hljs-keyword">await</span> FileSystem.readAsStringAsync(fileUri, {
      encoding: FileSystem.EncodingType.Base64,
    });

    <span class="hljs-keyword">const</span> message = <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'load-pdf'</span>,
      pdfData: <span class="hljs-string">`data:application/pdf;base64,<span class="hljs-subst">${base64Data}</span>`</span>,
    });

    webViewRef.current.postMessage(message);
  }, [fileUri]);
};
</code></pre>
<h3 id="heading-2-text-chunking-and-rag-implementation">2. Text Chunking and RAG Implementation</h3>
<p>Once text is extracted, it needs to be intelligently chunked for RAG:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> chunkText = (text: <span class="hljs-built_in">string</span>, documentId: <span class="hljs-built_in">string</span>): TextChunk[] =&gt; {
<span class="hljs-comment">// a simple mechanism to split text (can be improved, eg. split by paragraphs,...)</span>
  <span class="hljs-keyword">const</span> sentences = text.split(<span class="hljs-regexp">/[.!?]+/</span>).filter(<span class="hljs-function">(<span class="hljs-params">s</span>) =&gt;</span> s.trim().length &gt; <span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> chunks: TextChunk[] = [];
  <span class="hljs-keyword">const</span> chunkSize = <span class="hljs-number">400</span>;

  <span class="hljs-keyword">let</span> currentChunk = <span class="hljs-string">''</span>;
  <span class="hljs-keyword">let</span> chunkIndex = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> sentence <span class="hljs-keyword">of</span> sentences) {
    <span class="hljs-keyword">if</span> (currentChunk.length + sentence.length + <span class="hljs-number">1</span> &gt; chunkSize &amp;&amp; currentChunk.length &gt; <span class="hljs-number">0</span>) {
      chunks.push({
        id: <span class="hljs-string">`<span class="hljs-subst">${documentId}</span>_chunk_<span class="hljs-subst">${chunkIndex}</span>`</span>,
        documentId,
        content: currentChunk.trim(),
        metadata: {
          pageNumber: <span class="hljs-number">1</span>,
          section: <span class="hljs-string">'main'</span>,
          wordCount: currentChunk.split(<span class="hljs-regexp">/\s+/</span>).length,
        },
      });
      currentChunk = sentence + <span class="hljs-string">'. '</span>;
      chunkIndex++;
    } <span class="hljs-keyword">else</span> {
      currentChunk += sentence + <span class="hljs-string">'. '</span>;
    }
  }
  <span class="hljs-keyword">return</span> chunks;
};
</code></pre>
<h3 id="heading-3-on-device-ai-with-executorch">3. On-Device AI with ExecuTorch</h3>
<p>The heart of the offline chat capability is <a target="_blank" href="https://docs.swmansion.com/react-native-executorch/">ExecuTorch</a>, which allows running AI models directly on the device:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useLLM = ({ preventLoad = <span class="hljs-literal">false</span> }): <span class="hljs-function"><span class="hljs-params">CustomLLMType</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> controllerInstance = useMemo(
    <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">new</span> LLMModule({
      tokenCallback: <span class="hljs-function">(<span class="hljs-params">newToken: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
        setToken(newToken);
        setResponse(<span class="hljs-function">(<span class="hljs-params">prevResponse</span>) =&gt;</span> prevResponse + newToken);
      },
      messageHistoryCallback: setMessageHistory,
    }),
    [tokenCallback]
  );

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (preventLoad) <span class="hljs-keyword">return</span>;

    (<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> controllerInstance.load(LLAMA3_2_1B, setDownloadProgress);
        setIsReady(<span class="hljs-literal">true</span>);
      } <span class="hljs-keyword">catch</span> (e) {
        setError(e);
      }
    })();
  }, [controllerInstance, preventLoad]);
};
</code></pre>
<h3 id="heading-4-state-management-with-zustand">4. State Management with Zustand</h3>
<p>The app uses Zustand with Immer for predictable state management, and persisted to device storage using MMKV:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useAppStore = create&lt;AppState&gt;()(
  zustandPersist(
    immer(<span class="hljs-function">(<span class="hljs-params">set, get</span>) =&gt;</span> ({
      documents: {
        items: [] <span class="hljs-keyword">as</span> PDFDocument[],
        selectedId: <span class="hljs-literal">null</span>,
        searchQuery: <span class="hljs-string">''</span>,
        sortBy: <span class="hljs-string">'date'</span>,
        sortOrder: <span class="hljs-string">'desc'</span>,
      },
      <span class="hljs-comment">// ... other state slices</span>
      actions: createActions(set, get),
    })),
    {
      name: <span class="hljs-string">'learnpdf-store'</span>,
      storage: createMMKVStorage(),
    }
  )
);
</code></pre>
<h2 id="heading-5the-chat-experience">5.The Chat Experience</h2>
<h3 id="heading-general-ai-chat-llmchatscreentsx">General AI Chat (<code>LLMChatScreen.tsx</code>)</h3>
<p>The general chat interface provides a clean, WhatsApp-like experience:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useLLM, LLAMA3_2_1B } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native-executorch'</span>;

<span class="hljs-comment">// in a the component</span>
<span class="hljs-keyword">const</span> llm = useLLM({ model: LLAMA3_2_1B });

<span class="hljs-keyword">const</span> handleSendMessage = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">if</span> (!inputText.trim() || !llm?.isReady || llm?.isGenerating) <span class="hljs-keyword">return</span>;

  <span class="hljs-keyword">try</span> {
    Keyboard.dismiss();
    <span class="hljs-keyword">await</span> llm?.sendMessage(inputText.trim());
    setInputText(<span class="hljs-string">''</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    Alert.alert(<span class="hljs-string">'Error'</span>, <span class="hljs-string">'Failed to send message. Please try again.'</span>);
  }
};
</code></pre>
<h3 id="heading-context-aware-tutor-chat-tutorchatscreentsx">Context-Aware Tutor Chat (<code>TutorChatScreen.tsx</code>)</h3>
<p>The tutor chat provides document-specific assistance:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleSendMessage = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> systemPrompt = <span class="hljs-string">`You are a helpful AI tutor assistant. Your role is to help students understand and learn from their PDF document content.`</span>;

  <span class="hljs-keyword">const</span> contextMessages = chatState.messages.slice(<span class="hljs-number">-5</span>);
  <span class="hljs-keyword">const</span> messages = [
    { role: <span class="hljs-string">'system'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>, content: systemPrompt },
    { role: <span class="hljs-string">'system'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>, content: <span class="hljs-string">`Document content for reference:\n\n<span class="hljs-subst">${<span class="hljs-built_in">document</span>.extractedText?.slice(<span class="hljs-number">0</span>, <span class="hljs-number">2000</span>) || <span class="hljs-string">'No document content available'</span>}</span>`</span> },
    ...contextMessages.map(<span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> ({
      role: msg.role === <span class="hljs-string">'user'</span> ? (<span class="hljs-string">'user'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>) : (<span class="hljs-string">'assistant'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>),
      content: msg.content,
    })),
    { role: <span class="hljs-string">'user'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>, content: userMessage.content },
  ];

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> llm.generate(messages);
};
</code></pre>
<h2 id="heading-6-ai-powered-content-generation">6. AI-Powered Content Generation</h2>
<p>Beyond chat capabilities, the app generates various study materials using AI. The content generation system follows a consistent pattern across different tools while maintaining type safety and a simple error handling.</p>
<h3 id="heading-summary-generation-summaryscreentsx">Summary Generation (<code>SummaryScreen.tsx</code>)</h3>
<p>The summary generation provides concise document overviews:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleGenerateSummary = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">document</span> || !llm?.isReady) <span class="hljs-keyword">return</span>;

  setIsGeneratingSummary(<span class="hljs-literal">true</span>);
  setSummaryError(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> systemPrompt = <span class="hljs-string">`You are a helpful assistant that generates concise summaries of documents. 
    Summarize the key points, main concepts, and important information from the provided text. Keep the summary focused and relevant.`</span>;

    <span class="hljs-keyword">const</span> messages: Message[] = [
      {
        role: <span class="hljs-string">'system'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: systemPrompt,
      },
      {
        role: <span class="hljs-string">'user'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: <span class="hljs-string">`Please summarize the following document:
        \n\n<span class="hljs-subst">${<span class="hljs-built_in">document</span>.extractedText || <span class="hljs-string">''</span>}</span>`</span>,
      },
    ];

    <span class="hljs-keyword">const</span> summary: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">await</span> llm.generate(messages);
    <span class="hljs-keyword">const</span> updatedDocument: PDFDocument = { ...document, summary };

    setDocument(updatedDocument);
    StorageService.getInstance().updateDocument(updatedDocument);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error generating summary:'</span>, error);
    setSummaryError(<span class="hljs-string">'Failed to generate summary. Please try again.'</span>);
  } <span class="hljs-keyword">finally</span> {
    setIsGeneratingSummary(<span class="hljs-literal">false</span>);
  }
};
</code></pre>
<h3 id="heading-quiz-generation-quizscreentsx">Quiz Generation (<code>QuizScreen.tsx</code>)</h3>
<p>The quiz system generates interactive questions with multiple formats, this is a perfect example of structured output being used:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> QuizState {
  questions: Question[];
  currentQuestionIndex: <span class="hljs-built_in">number</span>;
  selectedAnswers: (<span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>)[];
  showResults: <span class="hljs-built_in">boolean</span>;
  score: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> handleGenerateQuiz = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">document</span> || !llm?.isReady) <span class="hljs-keyword">return</span>;

  setIsGeneratingQuiz(<span class="hljs-literal">true</span>);
  setQuizError(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> systemPrompt = <span class="hljs-string">`You are a helpful assistant that generates educational quiz questions. 
    Generate diverse, challenging questions based on the provided text using ONLY these question types:
    - multiple-choice (with 4 options)
    - true-false (with correctAnswer: 0 for true, 1 for false)
    - short-answer (with correctAnswer as a string)

    IMPORTANT: 
    - Return ONLY a valid JSON array
    - Use ONLY the exact type names: "multiple-choice", "true-false", "short-answer"
    - Do not include any explanatory text, markdown formatting, or additional commentary
    - Just the raw JSON array`</span>;

    <span class="hljs-keyword">const</span> messages: Message[] = [
      {
        role: <span class="hljs-string">'system'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: systemPrompt,
      },
      {
        role: <span class="hljs-string">'user'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: <span class="hljs-string">`Generate 5 quiz questions based on this document text:
        \n\n<span class="hljs-subst">${<span class="hljs-built_in">document</span>.extractedText || <span class="hljs-string">''</span>}</span>

        Return ONLY a JSON array using ONLY these three question types:
        [{
          "id": "unique_id",
          "type": "multiple-choice",
          "question": "Question text?",
          "options": ["Option A", "Option B", "Option C", "Option D"],
          "correctAnswer": 0,
          "explanation": "Explanation of correct answer",
          "difficulty": "medium"
        }]`</span>,
      },
    ];

    <span class="hljs-keyword">const</span> response: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">await</span> llm.generate(messages);
    <span class="hljs-keyword">const</span> questions: Question[] = parseQuizResponse(response);

    <span class="hljs-comment">// Save quiz to store</span>
    <span class="hljs-keyword">const</span> quiz: Quiz = {
      documentId: <span class="hljs-built_in">document</span>.id,
      title: <span class="hljs-string">`Quiz for <span class="hljs-subst">${<span class="hljs-built_in">document</span>.name}</span>`</span>,
      questions,
    };
    saveQuiz(quiz);

    setQuizState({
      questions,
      currentQuestionIndex: <span class="hljs-number">0</span>,
      selectedAnswers: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(questions.length).fill(<span class="hljs-literal">null</span>),
      showResults: <span class="hljs-literal">false</span>,
      score: <span class="hljs-number">0</span>,
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error generating quiz:'</span>, error);
    setQuizError(<span class="hljs-string">'Failed to generate quiz. Please try again.'</span>);
  } <span class="hljs-keyword">finally</span> {
    setIsGeneratingQuiz(<span class="hljs-literal">false</span>);
  }
};
</code></pre>
<p><strong>Quiz Response Parsing:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> parseQuizResponse = (response: <span class="hljs-built_in">string</span>): Question[] =&gt; {
  <span class="hljs-keyword">let</span> jsonStr: <span class="hljs-built_in">string</span> = response.trim();

  <span class="hljs-comment">// Handle markdown code blocks</span>
  <span class="hljs-keyword">if</span> (jsonStr.includes(<span class="hljs-string">'```json'</span>)) {
    <span class="hljs-keyword">const</span> jsonMatch = jsonStr.match(<span class="hljs-regexp">/```json\s*([\s\S]*?)\s*```/</span>);
    <span class="hljs-keyword">if</span> (jsonMatch) {
      jsonStr = jsonMatch[<span class="hljs-number">1</span>].trim();
    }
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (jsonStr.includes(<span class="hljs-string">'```'</span>)) {
    <span class="hljs-keyword">const</span> codeMatch = jsonStr.match(<span class="hljs-regexp">/```\s*([\s\S]*?)\s*```/</span>);
    <span class="hljs-keyword">if</span> (codeMatch) {
      jsonStr = codeMatch[<span class="hljs-number">1</span>].trim();
    }
  }

  <span class="hljs-comment">// Extract JSON array if not at start</span>
  <span class="hljs-keyword">if</span> (!jsonStr.startsWith(<span class="hljs-string">'['</span>)) {
    <span class="hljs-keyword">const</span> arrayMatch = jsonStr.match(<span class="hljs-regexp">/\[[\s\S]*\]/</span>);
    <span class="hljs-keyword">if</span> (arrayMatch) {
      jsonStr = arrayMatch[<span class="hljs-number">0</span>];
    }
  }

  <span class="hljs-keyword">const</span> questionsData: RawQuestion[] = <span class="hljs-built_in">JSON</span>.parse(jsonStr);
  <span class="hljs-keyword">return</span> questionsData.map((q, index): <span class="hljs-function"><span class="hljs-params">Question</span> =&gt;</span> ({
    id: q.id || <span class="hljs-string">`q_<span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>_<span class="hljs-subst">${index}</span>`</span>,
    <span class="hljs-keyword">type</span>: q.type === <span class="hljs-string">'true/false'</span> ? <span class="hljs-string">'true-false'</span> : q.type || <span class="hljs-string">'multiple-choice'</span>,
    question: q.question || <span class="hljs-string">`Question <span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span> about the document content?`</span>,
    options: q.options || [<span class="hljs-string">'Option A'</span>, <span class="hljs-string">'Option B'</span>, <span class="hljs-string">'Option C'</span>, <span class="hljs-string">'Option D'</span>],
    correctAnswer: q.correctAnswer || <span class="hljs-number">0</span>,
    explanation: q.explanation || <span class="hljs-string">'This question was generated based on the document content.'</span>,
    difficulty: q.difficulty || <span class="hljs-string">'medium'</span>,
  }));
};
</code></pre>
<h3 id="heading-mind-map-generation-mindmapscreentsx">Mind Map Generation (<code>MindMapScreen.tsx</code>)</h3>
<p>The mind map system creates hierarchical visual representations:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> MindMapState {
  nodes: MindMapNode[];
  expandedNodes: <span class="hljs-built_in">Set</span>&lt;<span class="hljs-built_in">string</span>&gt;;
}

<span class="hljs-keyword">const</span> handleGenerateMindMap = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">document</span> || !llm?.isReady) <span class="hljs-keyword">return</span>;

  setIsGeneratingMindMap(<span class="hljs-literal">true</span>);
  setMindMapError(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> systemPrompt = <span class="hljs-string">`You are a helpful assistant that creates hierarchical mind maps. 
    Extract key concepts and organize them into a logical hierarchy to help visualize the structure of information.
    Always create at least one root node with children to make the mind map interactive.`</span>;

    <span class="hljs-keyword">const</span> messages: Message[] = [
      {
        role: <span class="hljs-string">'system'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: systemPrompt,
      },
      {
        role: <span class="hljs-string">'user'</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
        content: <span class="hljs-string">`Create an interactive mind map structure from this document text:
        \n\n<span class="hljs-subst">${<span class="hljs-built_in">document</span>.extractedText || <span class="hljs-string">''</span>}</span>

        REQUIREMENTS:
        1. Create a hierarchical structure with at least 3 levels
        2. Include a main root node (level 0) with meaningful children
        3. Each parent node should have 2-4 child nodes
        4. Use meaningful, concise labels (max 3-4 words)

        Return ONLY a JSON array with this EXACT structure:
        [
          {
            "id": "root",
            "label": "Main Topic",
            "children": ["topic1", "topic2"],
            "parent": null,
            "level": 0
          }
        ]`</span>,
      },
    ];

    <span class="hljs-keyword">const</span> response: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">await</span> llm.generate(messages);
    <span class="hljs-keyword">const</span> processedNodes: MindMapNode[] = parseMindMapResponse(response);

    <span class="hljs-comment">// Find root nodes and set up initial expansion</span>
    <span class="hljs-keyword">const</span> rootNodeIds: <span class="hljs-built_in">string</span>[] = processedNodes
      .filter(<span class="hljs-function">(<span class="hljs-params">node: MindMapNode</span>) =&gt;</span> !node.parent || node.parent === <span class="hljs-literal">null</span>)
      .map(<span class="hljs-function">(<span class="hljs-params">node: MindMapNode</span>) =&gt;</span> node.id);

    setMindMapState({
      nodes: processedNodes,
      expandedNodes: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(rootNodeIds), <span class="hljs-comment">// Expand all root nodes by default</span>
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error generating mind map:'</span>, error);
    setMindMapError(<span class="hljs-string">'Failed to generate mind map. Please try again.'</span>);
  } <span class="hljs-keyword">finally</span> {
    setIsGeneratingMindMap(<span class="hljs-literal">false</span>);
  }
};
</code></pre>
<h2 id="heading-performance-optimizations">Performance Optimizations</h2>
<ol>
<li><p><strong>Lazy Loading</strong>: AI models are only loaded when needed</p>
</li>
<li><p><strong>Chunked Processing</strong>: Large PDFs are processed in chunks to prevent UI blocking</p>
</li>
<li><p><strong>Efficient Storage</strong>: MMKV provides faster read/write operations than AsyncStorage</p>
</li>
<li><p><strong>Memoization</strong>: Used React.memo and useMemo for expensive computations</p>
</li>
</ol>
<h2 id="heading-lessons-learned">Lessons Learned</h2>
<h3 id="heading-what-worked-well">What Worked Well</h3>
<ol>
<li><p><strong>React Native ExecuTorch Integration</strong>: Provided excellent offline AI capabilities</p>
</li>
<li><p><strong>MMKV Storage</strong>: Significantly improved performance over AsyncStorage</p>
</li>
<li><p><strong>Webview PDF Parsing</strong>: Ensured compatibility with various PDF formats</p>
</li>
<li><p><strong>Zustand State Management</strong>: Simplified state management with great performance</p>
</li>
</ol>
<h2 id="heading-future-enhancements">Future Enhancements</h2>
<ol>
<li><p><strong>Vector Embeddings</strong>: Implement proper vector embeddings for better RAG</p>
</li>
<li><p><strong>Model Optimization</strong>: Quantize newer models for better performance</p>
</li>
<li><p><strong>Multi-Document Support</strong>: Allow chat across multiple documents</p>
</li>
<li><p><strong>Fix Cross-Platform Issues</strong>: Some native features behave differently on iOS vs Android</p>
</li>
<li><p><strong>Model Size &amp; Memory Constraints</strong>: AI models are large and require careful download management, and device capabilities need to be checked in advance to select the appropriate model. Right now the model might make the app crash if there’s not enough available memory on the device.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building an offline AI chat tool required solving a few challenges in PDF processing, AI model management, and mobile performance optimization. The result is a working study assistant that works entirely offline while providing intelligent, context-aware responses.</p>
<p>React Native Executorch doesn’t currently leverage native GPU acceleration available on the latest iPhones and Android devices, but provides good quality for testing ground and proof of concept. Next step would be to integrate with Apple’s CoreML backend and see how it compares.</p>
<p>The LearnPDF app is available on both <a target="_blank" href="https://apps.apple.com/fr/app/learnpdf/id6752225685?l=en-GB">iOS App Store</a> and <a target="_blank" href="https://play.google.com/apps/testing/tech.glap.learnpdf">Google Play Store</a> (testers needed). If you want to try it out, but keep in mind that it has not been tested on multiple devices.</p>
]]></content:encoded></item><item><title><![CDATA[Using Raspberry Pi Pico W to send data via Bluetooth to a SwiftUI app]]></title><description><![CDATA[The Raspberry Pi Pico W is a versatile microcontroller with built-in Bluetooth Low Energy (BLE) capabilities, making it ideal for IoT applications. The Raspberry Pi Pico SDK comes with Bluetooth and it would be great to use it to send some data to an...]]></description><link>https://blog.abijah.me/using-raspberry-pi-pico-w-to-send-data-via-bluetooth-to-a-swiftui-app</link><guid isPermaLink="true">https://blog.abijah.me/using-raspberry-pi-pico-w-to-send-data-via-bluetooth-to-a-swiftui-app</guid><category><![CDATA[ble]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[raspberry-pi-pico]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[bluetooth]]></category><category><![CDATA[Bluetooth Low Energy]]></category><category><![CDATA[iot]]></category><dc:creator><![CDATA[Abijah Kajabika]]></dc:creator><pubDate>Fri, 23 Aug 2024 09:48:00 GMT</pubDate><content:encoded><![CDATA[<p>The Raspberry Pi Pico W is a versatile microcontroller with built-in Bluetooth Low Energy (BLE) capabilities, making it ideal for IoT applications. The Raspberry Pi Pico SDK comes with Bluetooth and it would be great to use it to send some data to an iPhone app. In order to connect to iOS, we need to use Bluetooth Low Energy (BLE), that means we'll transfer data through the GATT protocol.</p>
<h4 id="heading-prerequisites">Prerequisites</h4>
<ul>
<li><p>A Raspberry Pi Pico W</p>
</li>
<li><p>A macOS computer with Xcode installed</p>
</li>
<li><p>Basic knowledge of C++ and Swift</p>
</li>
<li><p>An iOS device for testing</p>
</li>
</ul>
<h3 id="heading-setting-up-the-raspberry-pi-pico-w">Setting Up the Raspberry Pi Pico W</h3>
<p>First, let's set up the Raspberry Pi Pico W to act as a BLE peripheral, sending data to a SwiftUI app.</p>
<ol>
<li><p><strong>Install the Pico SDK and Toolchain</strong>:</p>
<p> Follow the official <a target="_blank" href="https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html">Raspberry Pi Pico C/C++ SDK documentation</a> to set up your development environment. This includes installing the Pico SDK and necessary toolchains on your development machine. You can follow this <a target="_blank" href="https://vanhunteradams.com/Pico/Setup/PicoSetupMac.html">awesome guide</a>, but overall you should install the <a target="_blank" href="https://github.com/raspberrypi/pico-sdk">Raspberry Pi Pico SDK</a> if not already and make sure you have Xcode installed on your computer (as we'll write an App for iOS).</p>
</li>
<li><p><strong>Create a BLE Peripheral Project</strong>:</p>
<p> Start by creating a new C++ project for the Raspberry Pi Pico W. Here’s an example of how you can set up a simple BLE peripheral which sets up a BLE GATT server on the Pico W to send data.</p>
<pre><code class="lang-cpp"> <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/stdlib.h"</span></span>
 <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"pico/cyw43_arch.h"</span></span>
 <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"hardware/gpio.h"</span></span>
 <span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"btstack.h"</span></span>

 <span class="hljs-keyword">static</span> <span class="hljs-keyword">btstack_packet_callback_registration_t</span> hci_event_callback_registration;

 <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint16_t</span> gatt_service_uuid = <span class="hljs-number">0x181D</span>;
 <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint16_t</span> gatt_characteristic_uuid = <span class="hljs-number">0x2A19</span>;

 <span class="hljs-keyword">static</span> <span class="hljs-keyword">uint8_t</span> adv_data[] = {
     <span class="hljs-number">0x02</span>, <span class="hljs-number">0x01</span>, <span class="hljs-number">0x06</span>, <span class="hljs-comment">// Flags: LE General Discoverable Mode, BR/EDR Not Supported</span>
     <span class="hljs-number">0x0B</span>, <span class="hljs-number">0x09</span>, <span class="hljs-string">'P'</span>, <span class="hljs-string">'i'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'o'</span>, <span class="hljs-string">'W'</span>, <span class="hljs-string">'-'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'L'</span>, <span class="hljs-string">'E'</span>, <span class="hljs-comment">// Complete Local Name</span>
     <span class="hljs-number">0x03</span>, <span class="hljs-number">0x03</span>, <span class="hljs-number">0x1D</span>, <span class="hljs-number">0x18</span>  <span class="hljs-comment">// Complete List of 16-bit Service Class UUIDs</span>
 };

 <span class="hljs-keyword">static</span> <span class="hljs-keyword">uint8_t</span> value = <span class="hljs-number">42</span>; <span class="hljs-comment">// Example data to send</span>

 <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">att_read_callback</span><span class="hljs-params">(<span class="hljs-keyword">hci_con_handle_t</span> connection_handle, <span class="hljs-keyword">uint16_t</span> attribute_handle,
                              <span class="hljs-keyword">uint16_t</span> offset, <span class="hljs-keyword">uint8_t</span> * buffer, <span class="hljs-keyword">uint16_t</span> buffer_size)</span> </span>{
     <span class="hljs-keyword">if</span> (buffer) {
         buffer[<span class="hljs-number">0</span>] = value;
     }
     <span class="hljs-keyword">return</span> <span class="hljs-keyword">sizeof</span>(value);
 }

 <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">att_write_callback</span><span class="hljs-params">(<span class="hljs-keyword">hci_con_handle_t</span> connection_handle, <span class="hljs-keyword">uint16_t</span> attribute_handle,
                               <span class="hljs-keyword">uint16_t</span> transaction_mode, <span class="hljs-keyword">uint16_t</span> offset, <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> * buffer, <span class="hljs-keyword">uint16_t</span> buffer_size)</span> </span>{
     <span class="hljs-keyword">if</span> (buffer_size == <span class="hljs-number">1</span>) {
         value = buffer[<span class="hljs-number">0</span>];
     }
     <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
 }

 <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setup_gatt_server</span><span class="hljs-params">()</span> </span>{
     <span class="hljs-comment">// GATT Service</span>
     <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> gatt_service[] = {
         <span class="hljs-number">0x09</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0xFF</span>, <span class="hljs-number">0xFF</span>, <span class="hljs-number">0x04</span>, <span class="hljs-number">0x00</span>,
         <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x18</span>, <span class="hljs-number">0x1D</span>, <span class="hljs-number">0x00</span>
     };

     <span class="hljs-comment">// GATT Characteristic</span>
     <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint8_t</span> gatt_characteristic[] = {
         <span class="hljs-number">0x08</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x02</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x19</span>, <span class="hljs-number">0x2A</span>, <span class="hljs-number">0x02</span>,
         <span class="hljs-number">0x00</span>, <span class="hljs-number">0x01</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x19</span>, <span class="hljs-number">0x2A</span>, <span class="hljs-number">0x08</span>, <span class="hljs-number">0x00</span>
     };

     <span class="hljs-keyword">static</span> <span class="hljs-keyword">uint16_t</span> att_handle = <span class="hljs-number">0x0001</span>;

     att_server_init(att_read_callback, att_write_callback);
     att_db_util_add_service_uuid16(gatt_service_uuid);
     att_db_util_add_characteristic_uuid16(gatt_characteristic_uuid, ATT_PROPERTY_READ | ATT_PROPERTY_WRITE, ATT_SECURITY_NONE, &amp;att_handle, <span class="hljs-keyword">sizeof</span>(value), &amp;value);
 }

 <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">packet_handler</span><span class="hljs-params">(<span class="hljs-keyword">uint8_t</span> packet_type, <span class="hljs-keyword">uint16_t</span> channel, <span class="hljs-keyword">uint8_t</span> *packet, <span class="hljs-keyword">uint16_t</span> size)</span> </span>{
     <span class="hljs-keyword">if</span> (packet_type == HCI_EVENT_PACKET &amp;&amp; hci_event_packet_get_type(packet) == HCI_EVENT_LE_META) {
         <span class="hljs-keyword">if</span> (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) {
             <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Connected\n"</span>);
         }
     }
 }

 <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{
     stdio_init_all();
     <span class="hljs-keyword">if</span> (cyw43_arch_init()) {
         <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Failed to initialize\n"</span>);
         <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
     }

     l2cap_init();
     sm_init();
     setup_gatt_server();
     hci_event_callback_registration.callback = &amp;packet_handler;
     hci_add_event_handler(&amp;hci_event_callback_registration);
     hci_power_control(HCI_POWER_ON);

     gap_advertisements_set_params(<span class="hljs-number">0x0800</span>, <span class="hljs-number">0x0800</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x07</span>);
     gap_advertisements_set_data(<span class="hljs-keyword">sizeof</span>(adv_data), adv_data);
     gap_advertisements_enable(<span class="hljs-number">1</span>);

     <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
         cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, <span class="hljs-number">1</span>);
         sleep_ms(<span class="hljs-number">1000</span>);
         cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, <span class="hljs-number">0</span>);
         sleep_ms(<span class="hljs-number">1000</span>);
     }

     <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
 }
</code></pre>
<p> This C++ code initializes the Pico W, sets up BLE advertising, and sends a simple "Hello from Pico W" message whenever a device connects.</p>
</li>
<li><p><strong>Build and Flash the Code</strong>:</p>
<p> Compile the code using CMake and flash it to the Raspberry Pi Pico W. You can use the following commands in your terminal:</p>
<pre><code class="lang-bash"> mkdir build
 <span class="hljs-built_in">cd</span> build
 cmake ..
 make
 sudo picotool load &lt;your_program.uf2&gt;
</code></pre>
</li>
</ol>
<h3 id="heading-building-the-swiftui-app">Building the SwiftUI App</h3>
<p>Next, we’ll create a SwiftUI app to connect to the Raspberry Pi Pico W and receive data over BLE.</p>
<ol>
<li><p><strong>Create a New SwiftUI Project</strong>:</p>
<p> Open Xcode and create a new SwiftUI project named "PicoWGATT".</p>
</li>
<li><p><strong>Add Bluetooth Permissions</strong>:</p>
<p> In the <code>Info.plist</code> file, add the following key to request Bluetooth permissions:</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>NSBluetoothAlwaysUsageDescription<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>We need Bluetooth to connect to your Raspberry Pi Pico W<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
</code></pre>
</li>
<li><p><strong>Set Up CoreBluetooth in SwiftUI</strong>:</p>
<p> Now, implement the SwiftUI app to scan for the Raspberry Pi Pico W, connect to it, and display the received data.</p>
<pre><code class="lang-swift"> <span class="hljs-keyword">import</span> SwiftUI
 <span class="hljs-keyword">import</span> CoreBluetooth

 <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BLEManager</span>: <span class="hljs-title">NSObject</span>, <span class="hljs-title">ObservableObject</span>, <span class="hljs-title">CBCentralManagerDelegate</span>, <span class="hljs-title">CBPeripheralDelegate</span> </span>{
     <span class="hljs-keyword">var</span> centralManager: <span class="hljs-type">CBCentralManager!</span>
     <span class="hljs-keyword">var</span> picoWPeripheral: <span class="hljs-type">CBPeripheral?</span>
     @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> receivedData: <span class="hljs-type">String</span> = <span class="hljs-string">"No Data"</span>

     <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>() {
         <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>()
         centralManager = <span class="hljs-type">CBCentralManager</span>(delegate: <span class="hljs-keyword">self</span>, queue: <span class="hljs-literal">nil</span>)
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManagerDidUpdateState</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager)</span></span> {
         <span class="hljs-keyword">if</span> central.state == .poweredOn {
             centralManager.scanForPeripherals(withServices: [<span class="hljs-type">CBUUID</span>(string: <span class="hljs-string">"181D"</span>)])
         }
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManager</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: <span class="hljs-keyword">Any</span>], rssi RSSI: NSNumber)</span></span> {
         picoWPeripheral = peripheral
         picoWPeripheral?.delegate = <span class="hljs-keyword">self</span>
         centralManager.stopScan()
         centralManager.connect(peripheral, options: <span class="hljs-literal">nil</span>)
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">centralManager</span><span class="hljs-params">(<span class="hljs-number">_</span> central: CBCentralManager, didConnect peripheral: CBPeripheral)</span></span> {
         peripheral.discoverServices([<span class="hljs-type">CBUUID</span>(string: <span class="hljs-string">"181D"</span>)])
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didDiscoverServices error: Error?)</span></span> {
         <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> services = peripheral.services <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
         <span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> services {
             peripheral.discoverCharacteristics([<span class="hljs-type">CBUUID</span>(string: <span class="hljs-string">"2A19"</span>)], <span class="hljs-keyword">for</span>: service)
         }
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)</span></span> {
         <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> characteristics = service.characteristics <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
         <span class="hljs-keyword">for</span> characteristic <span class="hljs-keyword">in</span> characteristics {
             <span class="hljs-keyword">if</span> characteristic.uuid == <span class="hljs-type">CBUUID</span>(string: <span class="hljs-string">"2A19"</span>) {
                 peripheral.readValue(<span class="hljs-keyword">for</span>: characteristic)
             }
         }
     }

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">peripheral</span><span class="hljs-params">(<span class="hljs-number">_</span> peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)</span></span> {
         <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data = characteristic.value {
             <span class="hljs-keyword">let</span> receivedValue = data[<span class="hljs-number">0</span>]  <span class="hljs-comment">// Assuming data is a single byte</span>
             <span class="hljs-type">DispatchQueue</span>.main.async {
                 <span class="hljs-keyword">self</span>.receivedData = <span class="hljs-string">"Received: \(receivedValue)"</span>
             }
         }
     }
 }

 <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
     @<span class="hljs-type">ObservedObject</span> <span class="hljs-keyword">var</span> bleManager = <span class="hljs-type">BLEManager</span>()

     <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
         <span class="hljs-type">VStack</span> {
             <span class="hljs-type">Text</span>(<span class="hljs-string">"PicoW GATT Data"</span>)
                 .font(.title)
                 .padding()
             <span class="hljs-type">Text</span>(bleManager.receivedData)
                 .font(.largeTitle)
                 .padding()
         }
         .onAppear {
             bleManager.centralManagerDidUpdateState(bleManager.centralManager)
         }
     }
 }

 @main
 <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">PicoWGATTApp</span>: <span class="hljs-title">App</span> </span>{
     <span class="hljs-keyword">var</span> body: some <span class="hljs-type">Scene</span> {
         <span class="hljs-type">WindowGroup</span> {
             <span class="hljs-type">ContentView</span>()
         }
     }
 }
</code></pre>
<p> This code uses <code>CoreBluetooth</code> to scan for BLE peripherals, connect to the Raspberry Pi Pico W, and display the received data in the SwiftUI app.</p>
</li>
</ol>
<h3 id="heading-running-the-app">Running the App</h3>
<ol>
<li><p><strong>Run the C++ Program</strong>: Upload and run the C++ program on your Raspberry Pi Pico W. Just drag and drop the <code>.uf2</code> file to the Pico and it'll reboot.</p>
</li>
<li><p><strong>Build and Run the SwiftUI App</strong>: Build and run the SwiftUI app on your iOS device. It should detect the Raspberry Pi Pico W, connect to it, and display the data it receives.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>There you go, a quick view on how to use C++ on a Raspberry Pi Pico W to send data via Bluetooth Low Energy (BLE) to a SwiftUI app. This setup provides a simple way to communicate wirelessly between your Raspberry Pi Pico W and mobile devices, opening up a wide range of possibilities for IoT projects and real-time data communication.</p>
<p>Whether you're working on a sensor network, smart home automation, or educational projects, this combination of Raspberry Pi Pico W and SwiftUI allows for flexible and powerful development. If you code in React Native you can use the package <a target="_blank" href="https://github.com/innoveit/react-native-ble-manager"><code>react-native-ble-manager</code></a> to achieve the same functionality.</p>
]]></content:encoded></item><item><title><![CDATA[Animating map region with Animated API]]></title><description><![CDATA[This is a rewrite using functional components and PanGesture API of the example provided in the react-native-maps. I couldn't get the example provided in the package to properly work because it uses a custom pan controller.

If you already have a Rea...]]></description><link>https://blog.abijah.me/animating-map-region-with-animated-api</link><guid isPermaLink="true">https://blog.abijah.me/animating-map-region-with-animated-api</guid><category><![CDATA[React Native]]></category><category><![CDATA[google maps]]></category><category><![CDATA[Animated API]]></category><category><![CDATA[react-native-maps]]></category><dc:creator><![CDATA[Abijah Kajabika]]></dc:creator><pubDate>Mon, 03 Apr 2023 12:58:41 GMT</pubDate><content:encoded><![CDATA[<p>This is a rewrite using functional components and PanGesture API of <a target="_blank" href="https://github.com/react-native-maps/react-native-maps#using-the-mapview-with-the-animated-api">the example provided in the react-native-maps</a>. I couldn't get the example provided in the package to properly work because it uses a <a target="_blank" href="https://github.com/react-native-maps/react-native-maps/blob/master/example/src/examples/AnimatedViews.tsx">custom</a> pan controller.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682422520966/b7d6709a-d878-44b9-85c8-f4773d12ff95.gif" alt="The example we'll reproduce using hooks (functional component)" class="image--center mx-auto" /></p>
<p>If you already have a React Native project set up, you can skip this section.</p>
<h3 id="heading-setting-up-the-project">Setting up the project</h3>
<p>Start by <a target="_blank" href="https://reactnative.dev/docs/typescript">creating a react native project</a><br />Add react-native-maps to your project<br />Run <code>yarn add react-native-maps</code> and please follow <a target="_blank" href="https://github.com/react-native-maps/react-native-maps/blob/master/docs/installation.md">these instructions</a> to get it to properly work in your project.</p>
<h3 id="heading-implementation">Implementation</h3>
<p>Create a file named <code>useAnimatedRegion.tsx</code> and place in this content</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState, useMemo } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Animated, Dimensions } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native'</span>;
<span class="hljs-keyword">import</span> { AnimatedRegion, Region } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native-maps'</span>;

<span class="hljs-keyword">const</span> screen = Dimensions.get(<span class="hljs-string">'window'</span>);

<span class="hljs-keyword">const</span> ASPECT_RATIO = screen.width / screen.height;
<span class="hljs-keyword">const</span> LATITUDE_DELTA = <span class="hljs-number">0.0922</span>;
<span class="hljs-keyword">const</span> LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

<span class="hljs-keyword">const</span> ITEM_SPACING = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> ITEM_PREVIEW = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> ITEM_WIDTH = screen.width - <span class="hljs-number">2</span> * ITEM_SPACING - <span class="hljs-number">2</span> * ITEM_PREVIEW;
<span class="hljs-keyword">const</span> SNAP_WIDTH = ITEM_WIDTH + ITEM_SPACING;
<span class="hljs-keyword">const</span> BREAKPOINT1 = <span class="hljs-number">246</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> MarkerItem {
  id: <span class="hljs-built_in">number</span>;
  amount: <span class="hljs-built_in">number</span>;
  coordinate: {
    latitude: <span class="hljs-built_in">number</span>;
    longitude: <span class="hljs-built_in">number</span>;
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> AnimatedMapState {
  panX: Animated.Value;
  panY: Animated.Value;
  index: <span class="hljs-built_in">number</span>;
  canMoveHorizontal: <span class="hljs-built_in">boolean</span>;
  scrollY: Animated.AnimatedInterpolation;
  scrollX: Animated.AnimatedInterpolation;
  scale: Animated.AnimatedInterpolation;
  translateY: Animated.AnimatedInterpolation;
  markers: MarkerItem[];
  region: AnimatedRegion;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useAnimatedRegion = <span class="hljs-function">(<span class="hljs-params">
  initialRegion: Region,
  displayedMarkers: <span class="hljs-built_in">any</span>,
</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> initialState = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> panX = <span class="hljs-keyword">new</span> Animated.Value(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> panY = <span class="hljs-keyword">new</span> Animated.Value(<span class="hljs-number">0</span>);

    <span class="hljs-keyword">const</span> scrollY = panY.interpolate({
      inputRange: [<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>],
      outputRange: [<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>],
    });

    <span class="hljs-keyword">const</span> scrollX = panX.interpolate({
      inputRange: [<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>],
      outputRange: [<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>],
    });

    <span class="hljs-keyword">const</span> scale = scrollY.interpolate({
      inputRange: [<span class="hljs-number">0</span>, BREAKPOINT1],
      outputRange: [<span class="hljs-number">1</span>, <span class="hljs-number">1.6</span>],
      extrapolate: <span class="hljs-string">'clamp'</span>,
    });

    <span class="hljs-keyword">const</span> translateY = scrollY.interpolate({
      inputRange: [<span class="hljs-number">0</span>, BREAKPOINT1],
      outputRange: [<span class="hljs-number">0</span>, <span class="hljs-number">-100</span>],
      extrapolate: <span class="hljs-string">'clamp'</span>,
    });

    <span class="hljs-keyword">return</span> {
      panX,
      panY,
      index: <span class="hljs-number">0</span>,
      canMoveHorizontal: <span class="hljs-literal">true</span>,
      scrollY,
      scrollX,
      scale,
      translateY,
      markers: displayedMarkers,
      region: <span class="hljs-keyword">new</span> AnimatedRegion(initialRegion),
    };
  }, []);

  <span class="hljs-keyword">const</span> [state, setState] = useState&lt;AnimatedMapState&gt;(initialState);

  <span class="hljs-keyword">const</span> setListeners = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { region, panX, panY, scrollX, markers } = state;

    panX.addListener(onPanXChange);
    panY.addListener(onPanYChange);

    region.stopAnimation(<span class="hljs-function">() =&gt;</span> {});
    region
      .timing({
        latitude: scrollX.interpolate({
          inputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">_m: <span class="hljs-built_in">any</span>, i: <span class="hljs-built_in">any</span></span>) =&gt;</span> i * SNAP_WIDTH),
          outputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">m: <span class="hljs-built_in">any</span></span>) =&gt;</span> m.coordinate.latitude),
        }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
        longitude: scrollX.interpolate({
          inputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">_m: <span class="hljs-built_in">any</span>, i: <span class="hljs-built_in">any</span></span>) =&gt;</span> i * SNAP_WIDTH),
          outputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">m: <span class="hljs-built_in">any</span></span>) =&gt;</span> m.coordinate.longitude),
        }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
        useNativeDriver: <span class="hljs-literal">false</span>,
        duration: <span class="hljs-number">0</span>,
        toValue: <span class="hljs-number">0</span>,
        latitudeDelta: LATITUDE_DELTA,
        longitudeDelta: LONGITUDE_DELTA,
      })
      .start();
  };

  <span class="hljs-keyword">const</span> onPanXChange = <span class="hljs-function">(<span class="hljs-params">{ value }: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { index } = state;
    <span class="hljs-keyword">const</span> newIndex = <span class="hljs-built_in">Math</span>.floor((<span class="hljs-number">-1</span> * value + SNAP_WIDTH / <span class="hljs-number">2</span>) / SNAP_WIDTH);
    <span class="hljs-keyword">if</span> (index !== newIndex) {
      setState({ ...state, index: newIndex });
    }
  };

  <span class="hljs-keyword">const</span> onPanYChange = <span class="hljs-function">(<span class="hljs-params">{ value }: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { canMoveHorizontal, region, scrollY, scrollX, markers, index } =
      state;
    <span class="hljs-keyword">const</span> shouldBeMovable = <span class="hljs-built_in">Math</span>.abs(value) &lt; <span class="hljs-number">2</span>;
    <span class="hljs-keyword">if</span> (shouldBeMovable !== canMoveHorizontal) {
      setState({ ...state, canMoveHorizontal: shouldBeMovable });
      <span class="hljs-keyword">if</span> (!shouldBeMovable) {
        <span class="hljs-keyword">const</span> { coordinate } = markers[index];
        region.stopAnimation(<span class="hljs-function">() =&gt;</span> {});
        region
          .timing({
            latitude: scrollY.interpolate({
              inputRange: [<span class="hljs-number">0</span>, BREAKPOINT1],
              outputRange: [
                coordinate.latitude,
                coordinate.latitude - LATITUDE_DELTA * <span class="hljs-number">0.5</span> * <span class="hljs-number">0.375</span>,
              ],
              extrapolate: <span class="hljs-string">'clamp'</span>,
            }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
            latitudeDelta: scrollY.interpolate({
              inputRange: [<span class="hljs-number">0</span>, BREAKPOINT1],
              outputRange: [LATITUDE_DELTA, LATITUDE_DELTA * <span class="hljs-number">0.5</span>],
              extrapolate: <span class="hljs-string">'clamp'</span>,
            }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
            longitudeDelta: scrollY.interpolate({
              inputRange: [<span class="hljs-number">0</span>, BREAKPOINT1],
              outputRange: [LONGITUDE_DELTA, LONGITUDE_DELTA * <span class="hljs-number">0.5</span>],
              extrapolate: <span class="hljs-string">'clamp'</span>,
            }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
            useNativeDriver: <span class="hljs-literal">false</span>,
            duration: <span class="hljs-number">0</span>,
            toValue: <span class="hljs-number">0</span>,
            longitude: coordinate.longitude,
          })
          .start();
      } <span class="hljs-keyword">else</span> {
        region.stopAnimation(<span class="hljs-function">() =&gt;</span> {});
        region
          .timing({
            latitude: scrollX.interpolate({
              inputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">_m: <span class="hljs-built_in">any</span>, i: <span class="hljs-built_in">any</span></span>) =&gt;</span> i * SNAP_WIDTH),
              outputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">m: <span class="hljs-built_in">any</span></span>) =&gt;</span> m.coordinate.latitude),
            }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
            longitude: scrollX.interpolate({
              inputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">_m: <span class="hljs-built_in">any</span>, i: <span class="hljs-built_in">any</span></span>) =&gt;</span> i * SNAP_WIDTH),
              outputRange: markers.map(<span class="hljs-function">(<span class="hljs-params">m: <span class="hljs-built_in">any</span></span>) =&gt;</span> m.coordinate.longitude),
            }) <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>,
            useNativeDriver: <span class="hljs-literal">false</span>,
            duration: <span class="hljs-number">0</span>,
            toValue: <span class="hljs-number">0</span>,
            latitudeDelta: region.latitudeDelta,
            longitudeDelta: region.longitudeDelta,
          })
          .start();
      }
    }
  };
  useEffect(<span class="hljs-function">() =&gt;</span> {
    setListeners();
  }, []);

  <span class="hljs-keyword">return</span> state;
};
</code></pre>
<p>This hook helps handle all the region update business. The whole feature is happening in the <code>onPanYChange</code> function. This adjusts the latitude and deltas with the <code>panY</code> is changing (when the user is scrolling up). Those adjustments give the feeling that the map is zooming in. The interaction is smooth because internally the <code>AnimatedRegion</code> applies a timing of 1 millisecond.</p>
<p>Create a file named <code>AnimatedViews.tsx</code> file with this content</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// AnimatedViews.tsx</span>
<span class="hljs-keyword">import</span> React, { useMemo, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {
  StyleSheet,
  Dimensions,
  Animated,
  Text,
  PanResponder,
  View,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native'</span>;

<span class="hljs-keyword">import</span> {
  Animated <span class="hljs-keyword">as</span> AnimatedMap,
  Marker,
  PROVIDER_GOOGLE,
  Region,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-native-maps'</span>;

<span class="hljs-keyword">import</span> { useAnimatedRegion } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useAnimatedRegion'</span>;

<span class="hljs-keyword">const</span> screen = Dimensions.get(<span class="hljs-string">'window'</span>);

<span class="hljs-keyword">const</span> ASPECT_RATIO = screen.width / screen.height;
<span class="hljs-keyword">const</span> LATITUDE = <span class="hljs-number">37.78825</span>;
<span class="hljs-keyword">const</span> LONGITUDE = <span class="hljs-number">-122.4324</span>;
<span class="hljs-keyword">const</span> LATITUDE_DELTA = <span class="hljs-number">0.0922</span>;
<span class="hljs-keyword">const</span> LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

<span class="hljs-keyword">const</span> ITEM_SPACING = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> ITEM_PREVIEW = <span class="hljs-number">10</span>;
<span class="hljs-keyword">const</span> ITEM_WIDTH = screen.width - <span class="hljs-number">2</span> * ITEM_SPACING - <span class="hljs-number">2</span> * ITEM_PREVIEW;
<span class="hljs-keyword">const</span> ITEM_PREVIEW_HEIGHT = <span class="hljs-number">150</span>;

<span class="hljs-keyword">const</span> markersData = [
  {
    id: <span class="hljs-number">0</span>,
    amount: <span class="hljs-number">99</span>,
    coordinate: {
      latitude: LATITUDE,
      longitude: LONGITUDE,
    },
  },
  {
    id: <span class="hljs-number">1</span>,
    amount: <span class="hljs-number">199</span>,
    coordinate: {
      latitude: LATITUDE + <span class="hljs-number">0.004</span>,
      longitude: LONGITUDE - <span class="hljs-number">0.004</span>,
    },
  },
  {
    id: <span class="hljs-number">2</span>,
    amount: <span class="hljs-number">285</span>,
    coordinate: {
      latitude: LATITUDE - <span class="hljs-number">0.004</span>,
      longitude: LONGITUDE - <span class="hljs-number">0.004</span>,
    },
  },
];

<span class="hljs-keyword">const</span> AnimatedViews = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> state = useAnimatedRegion(
    {
      latitude: LATITUDE,
      longitude: LONGITUDE,
      latitudeDelta: LATITUDE_DELTA,
      longitudeDelta: LONGITUDE_DELTA,
    },
    markersData,
  );

  <span class="hljs-keyword">const</span> { markers, region, panY } = state;

  <span class="hljs-keyword">const</span> onRegionChange = <span class="hljs-function">(<span class="hljs-params">_region: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
    region.setValue(_region);
  };

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;AnimatedMap
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        region={region <span class="hljs-keyword">as</span> unknown <span class="hljs-keyword">as</span> Animated.WithAnimatedObject&lt;Region&gt;}
        onRegionChange={onRegionChange}
      &gt;
        {markers.map(<span class="hljs-function">(<span class="hljs-params">marker</span>) =&gt;</span> {
          <span class="hljs-keyword">return</span> (
            &lt;Marker key={marker.id} coordinate={marker.coordinate}&gt;
              &lt;&gt;
                &lt;Text style={styles.dollar}&gt;$&lt;/Text&gt;
                &lt;Text style={styles.amount}&gt;{marker.amount}&lt;/Text&gt;
              &lt;/&gt;
            &lt;/Marker&gt;
          );
        })}
      &lt;/AnimatedMap&gt;
      &lt;View style={styles.itemContainer}&gt;
        {markers.map(<span class="hljs-function">(<span class="hljs-params">marker</span>) =&gt;</span> (
          &lt;PanItem marker={marker} panY={panY} /&gt;
        ))}
      &lt;/View&gt;
    &lt;/&gt;
  );
};

<span class="hljs-keyword">const</span> styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
  },
  itemContainer: {
    flexDirection: <span class="hljs-string">'row'</span>,
    paddingHorizontal: ITEM_SPACING / <span class="hljs-number">2</span> + ITEM_PREVIEW,
    position: <span class="hljs-string">'absolute'</span>,
    top: screen.height - ITEM_PREVIEW_HEIGHT - <span class="hljs-number">64</span>,
  },
  map: {
    backgroundColor: <span class="hljs-string">'transparent'</span>,
    ...StyleSheet.absoluteFillObject,
  },
  item: {
    width: ITEM_WIDTH,
    height: screen.height + <span class="hljs-number">2</span> * ITEM_PREVIEW_HEIGHT,
    backgroundColor: <span class="hljs-string">'red'</span>,
    marginHorizontal: ITEM_SPACING / <span class="hljs-number">2</span>,
    overflow: <span class="hljs-string">'hidden'</span>,
    borderRadius: <span class="hljs-number">3</span>,
    borderColor: <span class="hljs-string">'#000'</span>,
  },

  dollar: {
    color: <span class="hljs-string">'#fff'</span>,
    fontSize: <span class="hljs-number">10</span>,
  },
  amount: {
    color: <span class="hljs-string">'#fff'</span>,
    fontSize: <span class="hljs-number">13</span>,
  },
});

<span class="hljs-keyword">const</span> PanItem = <span class="hljs-function">(<span class="hljs-params">{ marker, panY }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> localScroll = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> localPanY = <span class="hljs-keyword">new</span> Animated.Value(<span class="hljs-number">0</span>);

    <span class="hljs-keyword">const</span> scrollY = localPanY.interpolate({
      inputRange: [<span class="hljs-number">-1</span>, <span class="hljs-number">1</span>],
      outputRange: [<span class="hljs-number">1</span>, <span class="hljs-number">-1</span>],
    });

    <span class="hljs-keyword">const</span> translateY = scrollY.interpolate({
      inputRange: [<span class="hljs-number">0</span>, <span class="hljs-number">300</span>],
      outputRange: [<span class="hljs-number">0</span>, <span class="hljs-number">-100</span>],
      extrapolate: <span class="hljs-string">'clamp'</span>,
    });

    <span class="hljs-keyword">return</span> {
      localPanY,
      scrollY,
      translateY,
    };
  }, []);

  <span class="hljs-keyword">const</span> [state] = useState(localScroll);

  <span class="hljs-keyword">const</span> panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: <span class="hljs-function">() =&gt;</span> <span class="hljs-literal">true</span>,
      onPanResponderMove: <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span>
        [
          Animated.event([<span class="hljs-literal">null</span>, { dy: panY }], {
            useNativeDriver: <span class="hljs-literal">false</span>,
          }),
          Animated.event([<span class="hljs-literal">null</span>, { dy: state.localPanY }], {
            useNativeDriver: <span class="hljs-literal">false</span>,
          }),
        ].map(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> el?.(...args)),
      onPanResponderRelease: <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">//panY.extractOffset();</span>
      },
    }),
  ).current;

  <span class="hljs-keyword">return</span> (
    &lt;Animated.View
      key={marker.id}
      style={[
        styles.item,
        {
          transform: [{ translateY: state.translateY }],
        },
      ]}
      {...panResponder.panHandlers}
    /&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AnimatedViews;
</code></pre>
<p>Notice how to run multiple animated events with a <code>onPanResponderMove</code> or any event callback. The panX is not yet handled but a ScrollView might do the trick.</p>
<p>You can now import the component <code>&lt;AnimatedViews /&gt;</code> into your <code>App.tsx</code> file and play with it.</p>
]]></content:encoded></item></channel></rss>