<?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: Ninniko Labs</title>
    <description>The latest articles on DEV Community by Ninniko Labs (@goksel_koktas_4cd92edb6cb).</description>
    <link>https://dev.to/goksel_koktas_4cd92edb6cb</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%2F4216105%2F234fb86e-972c-4365-af50-81fbc726478a.png</url>
      <title>DEV Community: Ninniko Labs</title>
      <link>https://dev.to/goksel_koktas_4cd92edb6cb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/goksel_koktas_4cd92edb6cb"/>
    <language>en</language>
    <item>
      <title>What I learned building an ad-free, offline app for 2–4 year olds with Expo and Cloudflare</title>
      <dc:creator>Ninniko Labs</dc:creator>
      <pubDate>Thu, 29 Oct 2026 21:47:23 +0000</pubDate>
      <link>https://dev.to/goksel_koktas_4cd92edb6cb/what-i-learned-building-an-ad-free-offline-app-for-2-4-year-olds-with-expo-and-cloudflare-4ma7</link>
      <guid>https://dev.to/goksel_koktas_4cd92edb6cb/what-i-learned-building-an-ad-free-offline-app-for-2-4-year-olds-with-expo-and-cloudflare-4ma7</guid>
      <description>&lt;p&gt;Hi, I'm Göksel Köktaş, a full stack developer working with React Native, Expo, Next.js, NestJS and Cloudflare. After my baby tracking app Ninniko, I'm building the third app in the same family: &lt;strong&gt;Ninniko Fun Garden&lt;/strong&gt;, with games, read-aloud books, videos and songs for ages 2–4.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: no data from the child
&lt;/h2&gt;

&lt;p&gt;No analytics, no ads, no accounts, no push notifications, no crash reporting. That's Google Play's Families policy, and also my own choice. It rules out a lot of the tools we usually reach for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating content without OTA updates
&lt;/h2&gt;

&lt;p&gt;I wanted to add new videos, books and game content without shipping a new build. The answer was Cloudflare: files live in R2, and there are two tiny Workers. One is a public, read-only content server. The other is an admin page behind Cloudflare Access that only I can open. The app reads a plain JSON catalog with &lt;code&gt;fetch&lt;/code&gt;, downloads the files, stores them on the device and then runs fully offline. Videos only download on Wi-Fi. R2 has no egress fees, which suits this use case perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls with Expo SDK 57
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Media sessions pile up.&lt;/strong&gt; In &lt;code&gt;expo-audio&lt;/code&gt;, every player opens an Android media session. Calling &lt;code&gt;remove()&lt;/code&gt; isn't enough; sessions accumulate until they hit the ~100 limit and no sound plays at all. You also need &lt;code&gt;release()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AudioFlinger limits.&lt;/strong&gt; Several players per sound will hit the track limit (&lt;code&gt;AudioFlinger could not create track&lt;/code&gt;). Playing all voice lines through one shared player took me from 38 sessions down to 4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finger drawing freezes the phone.&lt;/strong&gt; &lt;code&gt;setState&lt;/code&gt; on every move plus one big SVG canvas is too much. Keeping the finger position in Reanimated, updating state about every 60 ms and splitting changing parts into small separate SVGs fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating video.&lt;/strong&gt; On Android the video surface (SurfaceView) ignores style rotation. For landscape video you have to actually rotate the screen with &lt;code&gt;expo-screen-orientation&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Designing for 2–4 year olds
&lt;/h2&gt;

&lt;p&gt;Every touch target is at least 64 dp. No reading required; instructions are spoken. No scores, no timers, no "you lost". While a character is talking, taps wait, so the child hears the whole instruction. The parent area is locked behind an addition question that is never read aloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating art and voice
&lt;/h2&gt;

&lt;p&gt;I generated the scenes with ChatGPT. For the short clips I used Veo 3.1 Lite on ElevenLabs, giving it our own scene images as start and end frames; voices are from ElevenLabs too. The biggest lesson: if an object isn't in the end frame, the model "throws it away" somewhere (the carrot ends up on the floor). Ending the clip on the same frame it started on, and only keeping the moving part of the scene, solved it.&lt;/p&gt;

&lt;p&gt;Ninniko (baby tracking, free, no ads): &lt;a href="https://play.google.com/store/apps/details?id=com.koktasg.ninniko" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.koktasg.ninniko&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ninniko Fun Garden is coming to Google Play soon.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was written with the help of AI, based on my own project notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>cloudflare</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
