<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://cafournier.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cafournier.com/" rel="alternate" type="text/html" /><updated>2026-10-26T21:26:22+00:00</updated><id>https://cafournier.com/feed.xml</id><title type="html">Charles-Adrien Fournier</title><subtitle>Charles-Adrien Fournier - iOS developer based in Paris</subtitle><author><name>Charles-Adrien Fournier</name></author><entry><title type="html">FitnessStats 2.8: Share your workouts</title><link href="https://cafournier.com/fitnessstats-2-8" rel="alternate" type="text/html" title="FitnessStats 2.8: Share your workouts" /><published>2026-10-26T00:00:00+00:00</published><updated>2026-10-26T00:00:00+00:00</updated><id>https://cafournier.com/fitnessstats-2-8</id><content type="html" xml:base="https://cafournier.com/fitnessstats-2-8"><![CDATA[<p>FitnessStats 2.8 is out. This update is about what happens after a workout: sharing it the way you want, as an image or as a video where your route draws itself, and handing your training to your AI assistant for an honest look at it. Here is a quick tour.</p>

<div class="post-feature">
<div class="post-feature-copy">
    <h2 id="share-any-workout">Share any workout</h2>

    <p>Tap a card on your dashboard, then tap one of its workouts: the share editor opens. Pick a layout, a format for stories, posts or a square, and a background: a color, your route on a map or a satellite view, or one of your photos.</p>

    <p>Everything on the image can be dragged, pinched and rotated, and you choose which numbers it shows. FitnessStats remembers your choices, so the next workout is ready to share in one tap. Share it, save it to Photos or copy it.</p>

    <p class="post-note">The Classic and Summary layouts are free. The other layouts and removing the watermark come with FitnessStats Premium.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fitnessstats-2-8/share.mp4" poster="/assets/images/posts/2026/fitnessstats-2-8/share-poster.webp" data-still="11" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FitnessStats screen recording: opening a 105 km ride from the Distance card, switching its layout, then trying a dark map and a blue background."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>A 105 km ride, from layout to background.</figcaption>
</figure>
</div>

<div class="post-feature post-feature-flip">
<div class="post-feature-copy">
    <h2 id="your-route-drawn-as-a-video">Your route, drawn as a video</h2>

    <p>Turn on <strong>Animate route</strong> in the Data tab, and Share or Save to Photos make a short video instead of an image: your route draws itself on the map, ready for a story. On long routes the camera follows you, then pulls back at the end to show the whole thing.</p>

    <p>Triathlons get the same treatment. The swim, the ride and the run draw one after the other, each with its own badge, and the image can show the distance and time of every leg.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fitnessstats-2-8/route.mp4" poster="/assets/images/posts/2026/fitnessstats-2-8/route-poster.webp" data-still="13.3" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FitnessStats screen recording: turning on Animate route for a marathon, then the route drawing itself on a satellite map of Paris."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>A marathon across Paris, drawing itself.</figcaption>
</figure>
</div>

<div class="post-feature">
<div class="post-feature-copy">
    <h2 id="export-to-ai">Export to AI</h2>

    <p>ChatGPT, Claude and Gemini are good at reading a training log, as long as you give them one. Open your profile and choose <strong>Export to AI</strong>. Pick what you want to know: an analysis of your progress, a review of your training, a 4-week training plan, or just the data. Choose a period and an activity, tap <strong>Copy for AI</strong>, then paste it into your assistant.</p>

    <p>The summary holds your totals, weekly volume, records and consistency, and the request is written in your language so the answer comes back in it. Nothing leaves your iPhone until you paste or share it, and GPS routes, device names and Health identifiers are never included.</p>

    <p>In Shortcuts, the new <strong>Get Workout Summary</strong> action returns the same summary, so an automation can hand it to your AI app.</p>

    <p class="post-note">Periods up to 90 days are free. Longer periods, and the raw CSV and JSON export, come with Premium.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fitnessstats-2-8/ai.mp4" poster="/assets/images/posts/2026/fitnessstats-2-8/ai-poster.webp" data-still="10.5" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FitnessStats screen recording: opening Export to AI from the profile, choosing Build a training plan and the last 90 days, then tapping Copy for AI."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>A training plan request, copied for your AI.</figcaption>
</figure>
</div>

<h2 id="also-in-this-update">Also in this update</h2>

<ul>
  <li>Triathlons now count their distance, calories and elevation gain in your stats.</li>
  <li>Tapping a dashboard card opens a cleaner detail: the headline figure, a chart over your period and the workouts behind it.</li>
  <li>Your profile now groups Export to AI and Share workouts under Tools.</li>
  <li>The upgrade screen and the widgets are now fully translated in every language FitnessStats speaks.</li>
  <li>FitnessStats now requires iOS 26.</li>
</ul>

<div class="post-app-cta">
    <img src="/assets/app-pages/fitnessstats-icon.png" width="64" height="64" alt="" decoding="async" />
    <div class="post-app-cta-copy">
        <strong>FitnessStats</strong>
        <span>Statistics for your Apple Health workouts</span>
    </div>
    <a class="post-app-cta-badge" href="https://apps.apple.com/app/apple-store/id6445907634?pt=2162580&amp;ct=cafournier&amp;mt=8"><img src="/assets/app-pages/appstore.svg" width="120" height="40" alt="Download FitnessStats on the App Store" /></a>
</div>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="FitnessStats" /><category term="Release notes" /><summary type="html"><![CDATA[FitnessStats 2.8 turns any workout into an image or a video where your route draws itself, and exports your training to ChatGPT, Claude or Gemini for an analysis or a training plan.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2026/fitnessstats-2-8/cover.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2026/fitnessstats-2-8/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">FridgeBuddy 2.35: Undo</title><link href="https://cafournier.com/fridgebuddy-2-35" rel="alternate" type="text/html" title="FridgeBuddy 2.35: Undo" /><published>2026-10-23T00:00:00+00:00</published><updated>2026-10-23T00:00:00+00:00</updated><id>https://cafournier.com/fridgebuddy-2-35</id><content type="html" xml:base="https://cafournier.com/fridgebuddy-2-35"><![CDATA[<p>FridgeBuddy 2.35 is out. This update is about small slips and interruptions: a wrong swipe is now one tap away from being undone, and FridgeBuddy comes back exactly where you were, even after iOS closed it. Here is what changed.</p>

<div class="post-feature">
<div class="post-feature-copy">
    <h2 id="undo-in-one-tap">Undo in one tap</h2>

    <p>Consuming, throwing away or deleting an item used to ask you to confirm first. Now it happens at once, and a banner at the bottom of the screen offers <strong>Undo</strong> for a few seconds. Tap it and the item is back in its storage as it was, with its dates, its photo and its reminder.</p>

    <p>If you asked FridgeBuddy to offer adding finished items to your shopping list, the same banner has an <strong>Add to List</strong> button, so finishing the milk no longer stops you with a question. You can also shake your iPhone to undo, or press ⌘Z with a keyboard on iPad.</p>

    <p>Deleting a storage is different: it removes every item in it, for your whole household if you share it, and it can’t be undone. So FridgeBuddy now asks first when the storage still has items.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fridgebuddy-2-35/undo.mp4" poster="/assets/images/posts/2026/fridgebuddy-2-35/undo-poster.webp" data-still="5.5" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FridgeBuddy screen recording: swiping to consume Milk in the Fridge, then tapping Undo in the banner that appears."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>Milk, consumed, then back in the fridge with one tap.</figcaption>
</figure>
</div>

<div class="post-feature post-feature-flip">
<div class="post-feature-copy">
    <h2 id="right-where-you-left-off">Right where you left off</h2>

    <p>When it needs memory, iOS quietly closes apps you haven’t used for a few minutes. It happens to every app, and until now FridgeBuddy then started over on Inventory. Not great if you locked your phone halfway through the shopping.</p>

    <p>FridgeBuddy now reopens on the tab you were using and on the shopping list you had open, as if it had never been closed.</p>

    <p class="post-note">If you close FridgeBuddy yourself in the app switcher, it still starts fresh.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fridgebuddy-2-35/reopen.mp4" poster="/assets/images/posts/2026/fridgebuddy-2-35/reopen-poster.webp" data-still="7" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FridgeBuddy screen recording: the Groceries shopping list, the Home Screen, then FridgeBuddy reopening on Groceries."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>Closed in the background, reopened on the same list.</figcaption>
</figure>
</div>

<h2 id="also-in-this-update">Also in this update</h2>

<ul>
  <li>Export as CSV in Settings writes dates the same way in every region, Excel shows accents correctly, and the file is named after the day you exported it.</li>
  <li>Storage widgets use FridgeBuddy’s orange instead of blue, and the Opened list shows its icon in widgets.</li>
  <li>Shopping lists offer <strong>Rename</strong> like storages do, a search inside a storage that finds nothing says so clearly, and the Lock Screen widget says “0 items”.</li>
</ul>

<div class="post-app-cta">
    <img src="/assets/app-pages/fridgebuddy-icon.png" width="64" height="64" alt="" decoding="async" />
    <div class="post-app-cta-copy">
        <strong>FridgeBuddy</strong>
        <a href="/fridgebuddy/">Learn more about the app</a>
    </div>
    <a class="post-app-cta-badge" href="https://apps.apple.com/app/apple-store/id1500190823?pt=2162580&amp;ct=cafournier&amp;mt=8"><img src="/assets/app-pages/appstore.svg" width="120" height="40" alt="Download FridgeBuddy on the App Store" /></a>
</div>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="FridgeBuddy" /><category term="Release notes" /><summary type="html"><![CDATA[FridgeBuddy 2.35 lets you undo a consume, throw away or delete in one tap, reopens on the shopping list you were using, and exports a cleaner CSV.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2026/fridgebuddy-2-35/cover.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2026/fridgebuddy-2-35/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">FridgeBuddy 2.34: Fruits and vegetables</title><link href="https://cafournier.com/fridgebuddy-2-34" rel="alternate" type="text/html" title="FridgeBuddy 2.34: Fruits and vegetables" /><published>2026-10-09T00:00:00+00:00</published><updated>2026-10-09T00:00:00+00:00</updated><id>https://cafournier.com/fridgebuddy-2-34</id><content type="html" xml:base="https://cafournier.com/fridgebuddy-2-34"><![CDATA[<p>FridgeBuddy 2.34 is out. This update is about the food that never comes with a printed date: fruits and vegetables now get a suggested expiration date. You can also keep a note on any item, and shopping lists stop interrupting you while you check things off. Here is a quick tour.</p>

<div class="post-feature">
<div class="post-feature-copy">
    <h2 id="dates-for-fruits-and-vegetables">Dates for fruits and vegetables</h2>

    <p>Strawberries, carrots or a head of lettuce rarely have a date on them, which is exactly why they get forgotten at the back of the fridge. Tap <strong>+</strong>, choose <strong>Fruits and Vegetables</strong> and tick what you bought. Each one gets a suggested expiration date based on the storage times in the USDA’s <a href="https://www.foodsafety.gov/keep-food-safe/foodkeeper-app">FoodKeeper</a>, with a hint such as “Once ripe, usually keeps 3–4 days in the fridge.”</p>

    <p>Typing the name of a fruit or vegetable when you add an item by hand suggests a date too. Every date can still be changed, and if your bananas always last longer than the suggestion, set your own keeping time for them in Settings.</p>

    <p class="post-note">If + opens the scanner directly, touch and hold it to see the menu.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fridgebuddy-2-34/produce.mp4" poster="/assets/images/posts/2026/fridgebuddy-2-34/produce-poster.webp" data-still="9" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FridgeBuddy screen recording: adding avocados and blueberries from the Fruits and Vegetables list."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>Avocados and blueberries, added with their suggested dates.</figcaption>
</figure>
</div>

<div class="post-feature post-feature-flip">
<div class="post-feature-copy">
    <h2 id="notes-on-your-items">Notes on your items</h2>

    <p>Any item can now carry a note: what it is for, where you put it, who it belongs to. Edit the item and write it in the last field of the form.</p>

    <p>The note then shows in the item’s details. Search and Spotlight find it, CSV exports include it, and it is shared with your household along with the rest of the item.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <div class="fb-note-screen" aria-hidden="true">
                <img class="fb-bar fb-status" src="/assets/images/posts/2026/fridgebuddy-2-34/status.webp" width="600" height="80" alt="" decoding="async" />
                <img class="fb-nav fb-nav-back" src="/assets/images/posts/2026/fridgebuddy-2-34/navback.webp" width="137" height="70" alt="" decoding="async" />
                <img class="fb-nav fb-nav-more" src="/assets/images/posts/2026/fridgebuddy-2-34/navmore.webp" width="137" height="70" alt="" decoding="async" />
                <p class="fb-note-title">Avocados</p>
                <div class="fb-note-card fb-note-fields">
                    <div class="fb-note-row"><span>Storage</span><span>Fridge</span></div>
                    <div class="fb-note-row"><span>Expiration Date</span><span>Oct 8, 2026</span></div>
                    <div class="fb-note-row"><span>Date Added</span><span>Oct 5, 2026</span></div>
                </div>
                <div class="fb-note-card fb-note-text">
                    <div class="fb-note-row">
                        <span class="fb-note-add" hidden="">Add Note</span>
                        <span class="fb-note-typed"><span class="fb-note-words">For Sunday’s guacamole</span><span class="fb-note-caret" hidden=""></span></span>
                    </div>
                </div>
                <img class="fb-bar fb-tabbar" src="/assets/images/posts/2026/fridgebuddy-2-34/tabbar.webp" width="600" height="118" alt="" decoding="async" />
            </div>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>A note on the avocados, for Sunday’s guacamole.</figcaption>
</figure>
</div>

<div class="post-feature">
<div class="post-feature-copy">
    <h2 id="quicker-shopping-lists">Quicker shopping lists</h2>

    <p>Checking an item off a shopping list no longer asks you anything, so you can tick things off as they go into the cart.</p>

    <p>When you’re done, one button adds everything you checked to your inventory. A short review lets you choose the storage and the dates, and fresh produce gets its suggested date there as well. The items then leave the list.</p>
  </div>
<figure class="post-phone-figure">
    <div class="post-phone">
        <div class="post-phone-screen">
            <video src="/assets/images/posts/2026/fridgebuddy-2-34/shopping.mp4" poster="/assets/images/posts/2026/fridgebuddy-2-34/shopping-poster.webp" data-still="5.6" width="600" height="1304" autoplay="" muted="" loop="" playsinline="" preload="auto" aria-label="FridgeBuddy screen recording: checking milk and bananas off the Groceries list, then adding both to the fridge."></video>
        </div>
        <button class="post-demo-toggle" type="button" aria-label="Pause animation" aria-pressed="false" hidden="">
    <svg class="post-icon-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.5" height="12" rx="1" /><rect x="8.5" y="1" width="3.5" height="12" rx="1" /></svg>
    <svg class="post-icon-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.8v10.4a.8.8 0 0 0 1.2.7l8.4-5.2a.8.8 0 0 0 0-1.4L4.2 1.1A.8.8 0 0 0 3 1.8Z" /></svg>
</button>

    </div>
    <figcaption>Milk and bananas, checked off and back in the fridge.</figcaption>
</figure>
</div>

<h2 id="polish-everywhere-else">Polish everywhere else</h2>

<p>Most screens got a round of care in this release:</p>

<ul>
  <li>Inventory rows behave like native iOS rows: touch and hold for the full context menu, and swipe for clearer actions.</li>
  <li>Expiration dates are colored by urgency, and an item’s details count days like the list does, with Today, Tomorrow and Yesterday.</li>
  <li>The header of an item’s details is easier to read.</li>
  <li>Insights shows correct figures again, its charts are easier to read, and you can share them.</li>
  <li>Settings, titles and sheets are more consistent, and FridgeBuddy asks before discarding changes you haven’t saved.</li>
  <li>On iPad, the tabs stay the same at every window size, Inventory shows your storages in a sidebar beside their items, and resizing a window keeps your place.</li>
</ul>

<div class="post-app-cta">
    <img src="/assets/app-pages/fridgebuddy-icon.png" width="64" height="64" alt="" decoding="async" />
    <div class="post-app-cta-copy">
        <strong>FridgeBuddy</strong>
        <a href="/fridgebuddy/">Learn more about the app</a>
    </div>
    <a class="post-app-cta-badge" href="https://apps.apple.com/app/apple-store/id1500190823?pt=2162580&amp;ct=cafournier&amp;mt=8"><img src="/assets/app-pages/appstore.svg" width="120" height="40" alt="Download FridgeBuddy on the App Store" /></a>
</div>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="FridgeBuddy" /><category term="Release notes" /><summary type="html"><![CDATA[FridgeBuddy 2.34 suggests expiration dates for fruits and vegetables, lets you keep a note on any item, and makes shopping lists quicker to check off.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2026/fridgebuddy-2-34/cover.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2026/fridgebuddy-2-34/cover.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">WWDC23 Recap - Beyond the basics of structured concurrency</title><link href="https://cafournier.com/beyond-the-basic-structured-concurrency" rel="alternate" type="text/html" title="WWDC23 Recap - Beyond the basics of structured concurrency" /><published>2023-06-12T00:00:00+00:00</published><updated>2023-06-12T00:00:00+00:00</updated><id>https://cafournier.com/beyond-the-basic-structured-concurrency</id><content type="html" xml:base="https://cafournier.com/beyond-the-basic-structured-concurrency"><![CDATA[<h2 id="beyond-the-basics-of-structured-concurrency">Beyond the basics of structured concurrency</h2>

<p><a href="https://developer.apple.com/videos/play/wwdc2023/10170/">https://developer.apple.com/videos/play/wwdc2023/10170/</a></p>

<h3 id="task-hierarchy">Task hierarchy</h3>

<p>Concurrent execution is triggered when</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Structured concurrency</span>
<span class="k">async</span> <span class="k">let</span> <span class="nv">future</span> <span class="o">=</span> <span class="o">...</span>

<span class="n">taskGroup</span><span class="o">.</span><span class="n">addTask</span> <span class="p">{</span>
	<span class="o">...</span>
<span class="p">}</span>

<span class="c1">// Unstructured concurrency</span>
<span class="kt">Task</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>

<span class="kt">Task</span><span class="o">.</span><span class="n">detached</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
</code></pre></div></div>

<p>Prefer structured task</p>

<h3 id="task-cancellation">Task cancellation</h3>

<p>Structured task are cancelled implicitly when leaving scope</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Polling</span>
<span class="kt">Task</span><span class="o">.</span><span class="n">isCancelled</span> 
<span class="k">try</span> <span class="kt">Task</span><span class="o">.</span><span class="nf">checkCancellation</span><span class="p">()</span>

<span class="c1">// Event-Driven</span>
<span class="nf">withTaskCancellationHandler</span><span class="p">(</span><span class="nv">operation</span><span class="p">:,</span> <span class="nv">onCancel</span><span class="p">:)</span>
</code></pre></div></div>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Cancellation and AsyncSequences</span>

<span class="kd">private</span> <span class="kd">final</span> <span class="kd">class</span> <span class="kt">OrderState</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
	<span class="k">let</span> <span class="nv">protectedIsRunning</span> <span class="o">=</span> <span class="kt">ManagedAtomic</span><span class="o">&lt;</span><span class="kt">Bool</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
	<span class="k">var</span> <span class="nv">isRunning</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
		<span class="k">get</span> <span class="p">{</span> <span class="n">protectedIsRunning</span><span class="o">.</span><span class="nf">load</span><span class="p">(</span><span class="nv">ordering</span><span class="p">:</span> <span class="o">.</span><span class="n">acquiring</span><span class="p">)</span> <span class="p">}</span>
		<span class="k">set</span> <span class="p">{</span> <span class="n">protectedIsRunning</span><span class="o">.</span><span class="nf">store</span><span class="p">(</span><span class="n">newValue</span><span class="p">,</span> <span class="nv">ordering</span><span class="p">:</span> <span class="o">.</span><span class="n">relaxed</span><span class="p">)</span> <span class="p">}</span>
	<span class="p">}</span>

	<span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">isRunning</span> <span class="o">=</span> <span class="kc">false</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Propagates through the task tree</p>

<p>Seamlessly integrates with throwing errors</p>

<p>Event-driven cancellation handlers or explicit polling</p>

<h3 id="task-priority">Task priority</h3>

<p>By default child task inherit priority from parent</p>

<p>Structured priority escalation</p>

<ul>
  <li>Lower priority tasks escalate priority when awaited on by a higher priority task</li>
  <li>Escalation propagates through the task tree</li>
  <li>Priority remains escalated until the task completes</li>
</ul>

<h3 id="task-group-patterns">Task group patterns</h3>

<p>DiscardingTaskGroup releases resources immediately on task completion</p>

<p>Use the completion of one task to signal the creation of the next in a TaskGroup</p>

<h3 id="task-local-values">Task-local values</h3>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Task-local values</span>
<span class="c1">// Associating values with task trees</span>
<span class="kd">actor</span> <span class="kt">Kitchen</span> <span class="p">{</span>
	<span class="kd">@TaskLocal</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">orderID</span><span class="p">:</span> <span class="kt">Int</span><span class="p">?</span>
	<span class="kd">@TaskLocal</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">cook</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
	
	<span class="kd">func</span> <span class="nf">logStatus</span><span class="p">()</span> <span class="p">{</span>
		<span class="nf">print</span><span class="p">(</span><span class="s">"Current cook: </span><span class="se">\(</span><span class="n">kitchen</span><span class="o">.</span><span class="n">cook</span> <span class="p">??</span> <span class="s">"none"</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">let</span> <span class="nv">kitchen</span> <span class="o">=</span> <span class="kt">Kitchen</span><span class="p">()</span>
<span class="k">await</span> <span class="n">kitchen</span><span class="o">.</span><span class="nf">logStatus</span><span class="p">()</span> <span class="c1">// Current cook: none</span>
<span class="k">await</span> <span class="kt">Kitchen</span><span class="o">.</span><span class="n">$cook</span><span class="o">.</span><span class="nf">withValue</span><span class="p">(</span><span class="s">"Sakura"</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">await</span> <span class="n">kitchen</span><span class="o">.</span><span class="nf">logStatus</span><span class="p">()</span> <span class="c1">// Current cook: Sakura</span>
<span class="p">}</span>
<span class="k">await</span> <span class="n">kitchen</span><span class="o">.</span><span class="nf">logStatus</span><span class="p">()</span> <span class="c1">// Current cook: none</span>
</code></pre></div></div>

<p>Attach metadata to the current task</p>

<p>Inherited by child tasks as well as <code class="language-plaintext highlighter-rouge">Task</code> {}</p>

<p>Low-level building block for context propagation</p>

<h3 id="task-traces">Task traces</h3>

<p>Local profiling with Instruments</p>

<p>Swift Distributed Tracing</p>

<ul>
  <li>Open source package for server ecosystem</li>
  <li>Provides an extensible instrumentation API</li>
  <li>Similar to Swift Log and Swift Metrics</li>
</ul>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="Swift" /><category term="Xcode" /><category term="iOS dev" /><summary type="html"><![CDATA[Beyond the basics of structured concurrency]]></summary></entry><entry><title type="html">Comment ajouter la frame d’un device sur vos screenshots</title><link href="https://cafournier.com/ajouter-frame-device-screenshot" rel="alternate" type="text/html" title="Comment ajouter la frame d’un device sur vos screenshots" /><published>2020-07-27T00:00:00+00:00</published><updated>2020-07-27T00:00:00+00:00</updated><id>https://cafournier.com/ajouter-frame-device-screenshot</id><content type="html" xml:base="https://cafournier.com/ajouter-frame-device-screenshot"><![CDATA[<p>Comment ajouter la frame d’un device (iPhone / iPad / Android) pour améliorer ses screenshots.</p>

<h2 id="pré-requis">Pré-requis</h2>

<p>Nous allons utiliser <a href="https://fastlane.tools">Fastlane</a> pour simplifier et automatiser l’ajout des devices sur nos screenshots. Fastlane est un outil qui permet d’automatiser le processus de mise en production d’une application mobile. Parmi toutes ses fonctionnalités, nous allons nous attarder sur <em><a href="https://docs.fastlane.tools/actions/frameit/">frameit</a></em>.</p>

<p>Je vous laisse lire <a href="https://docs.fastlane.tools/getting-started/ios/setup/">la documentation</a> de fastlane pour l’installation de celui-ci. Je vous conseille tout de même de faire une instalation globale afin de pouvoir l’utiliser depuis n’importe quel dossier.</p>

<h2 id="comment-utiliser-frameit">Comment utiliser Frameit</h2>

<h3 id="screenshot-diphone--ipad">Screenshot d’iPhone / iPad</h3>

<p>Une seule commande à retenir.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> fastlane frameit</code></pre></figure>

<p>Il vous suffit d’avoir vos différents screenshots dans un dossier, vous allez dans le dossier et vous lancez la commande ci-dessus.</p>

<p>Fastlane va passer chacun des screenshots en revu, en fonction de la taille il va ajouter la frame du device associé. Vos screenshots peuvent être pris avec le simulateur ou avec votre device physique cela ne change rien, il faut juste qu’ils aient la taille par défaut.</p>

<p>Vous pouvez trouver la liste des tailles de screenshot par type de device dans <a href="https://help.apple.com/app-store-connect/#/devd274dd925">la documentation</a> d’Apple</p>

<p>Il y a une petite subtilité pour les screenshots d’iPad Pro, en fonction de la génération de celui-ci vous aurez la version avec TouchID ou FaceID. Etant donné que la taille des screenshots est la même pour les deux devices, il vous suffit d’ajouter le type de device que vous souhaitez dans le nom du fichier pour que fastlane puisse ajouter la bonne frame.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">iPad Pro avec TouchID   -&gt; <span class="s1">'iPad Pro (12.9-inch) (2nd generation)'</span>

iPad Pro avec FaceID    -&gt; <span class="s1">'iPad Pro (12.9-inch) (3rd generation)'</span></code></pre></figure>

<h3 id="screenshot-dun-android">Screenshot d’un Android</h3>

<p>Il suffit de dire explicitement à fastlane que l’on veut une frame d’android. La logique est la même en fonction de la taille, le device sera différent.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="o">&gt;</span> fastlane frameit android</code></pre></figure>

<p>Dans mon cas, j’ai eu quelques soucis avec des screenshots en <mark>1080x1920</mark>. Après quelques recherches, il s’agit d’un bug. Pour le corriger, il faut modifier le fichier de configuration de fastlane.</p>

<p>Dans le dossier où est installé Fastlane, vous allez devoir modifier le fichier suivant.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">fastlane/frameit/latest/offsets.json</code></pre></figure>

<p>Il faut maintenant modifier la ligne <mark>Nexus 5x</mark> en <mark>Nexus 5X</mark>. L’objet final ressemble à cela.</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"Nexus 5X"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"offset"</span><span class="p">:</span><span class="w"> </span><span class="s2">"+53+231"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="w">
</span><span class="p">}</span><span class="err">,</span></code></pre></figure>

<p>Une fois que la modification est faite, vous pouvez relancer fastlane et ça devrait fonctionner.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Vous pouvez maintenant facilement ajouter la frame d’un device autour de vos screenshots. Cela permet de mettre en valeur vos screenshots rapidement.</p>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="tips" /><summary type="html"><![CDATA[Comment ajouter la frame d’un device (iPhone / iPad / Android) pour améliorer ses screenshots.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/framed-screenshots-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/framed-screenshots-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Project 6 : Learning / WWDC break</title><link href="https://cafournier.com/project-6-wwdc-break" rel="alternate" type="text/html" title="Project 6 : Learning / WWDC break" /><published>2020-07-15T00:00:00+00:00</published><updated>2020-07-15T00:00:00+00:00</updated><id>https://cafournier.com/project-6-wwdc-break</id><content type="html" xml:base="https://cafournier.com/project-6-wwdc-break"><![CDATA[<p>This month is a bit different I choose to not release any project, instead I use my time to learn new things.</p>

<h2 id="react--serverless-stack">React / Serverless stack</h2>

<p>I’ve spent half of the month learning React and the full process of building a serverless stack. I want to be able to release web app for my 12 startups challenge. I’m now more confident in my capacity of releasing a web product.</p>

<p>I’ve started by following <a href="https://serverless-stack.com">Serverless Stack</a>. It’s a great introduction to the serverless concept, at the end you finish with a full serverless web app relying on AWS Lambda.</p>

<h2 id="wwdc-2020">WWDC 2020</h2>

<p>This year’s WWDC was nice, I really enjoyed the new format. The improved design of iPadOS app’s motivated me to rethink my existings apps and improve the iPad experience.</p>

<p>Widget are also existing and I’m already working on adding widget support for Fridge Buddy.</p>

<p>Talks I’ve watched and really like</p>

<ul>
  <li><a href="https://developer.apple.com/wwdc20/10206">Design for iPad</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10041">What’s new in SwifUI</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10170">What’s new in Swift</a></li>
</ul>

<p>Talks I need to watch</p>

<ul>
  <li><a href="https://developer.apple.com/wwdc20/10174">Explore app clips</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10020">Make your app visually accessible</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10105">Build for iPad</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10172">Design great app clips</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10028">Meet WidgetKit</a></li>
  <li><a href="https://developer.apple.com/wwdc20/10103">Design great widgets</a></li>
</ul>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="entrepreneurship" /><category term="12 Startups" /><summary type="html"><![CDATA[This month is a bit different I choose to not release any project, instead I use my time to learn new things.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/project-6-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/project-6-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Project 5 : City Walks</title><link href="https://cafournier.com/project-5-city-walks" rel="alternate" type="text/html" title="Project 5 : City Walks" /><published>2020-07-04T00:00:00+00:00</published><updated>2020-07-04T00:00:00+00:00</updated><id>https://cafournier.com/project-5-city-walks</id><content type="html" xml:base="https://cafournier.com/project-5-city-walks"><![CDATA[<blockquote>
  <p>City walks list many walks, trek, hike available near you in order to breath after this quarantine time.</p>
</blockquote>

<h2 id="story-behind-this-project">Story behind this project</h2>

<p>For this month, I’ve wanted to make an app related to the current situation we live in the world. In France where I’m living, we approach the end of the quarantine but not the end oh the restrictions. For the first weeks post quarantine, we couldn’t move far than 100 kilometers away from our home.</p>

<p>After the quarantine, a lot of people wanted to go outside and enjoy nature, all the more for city dwellers who lives in small flat and can’t have a garden.</p>

<p>So I’ve wanted to make an application that lists walks and hikes available in a nearby radius.</p>

<p>I’ve started to look at <a href="http://www.data.gouv.fr">data.gouv.fr</a>, there is a lot of open data available. After a quick search, I found that some region share their PDIPR (Departemental plan for walking and hiking routes). In this dataset I managed to find walking routes with their geopath and a small description. This was exactly what I needed.</p>

<p>Regarding the app, the core features are really simple and was made quick. I took a bit of extra time to work on animations and transitions between each views.</p>

<p>For the first version of the app, I only work with a single dataset. It was more than enough to test the feature I’ve wanted. When the app was ready, I looked on data.gouv.fr for similar dataset from other regions. After few hours of searching, I can’t find any dataset similar to the one I use. They didn’t follow any kind of convention when making their dataset.</p>

<p>I’ve submited the app at the end of may, few days later the app was rejected because they found it kind of useless.</p>

<h2 id="next-steps">Next steps</h2>

<p>I will try to make a course about iOS development and will use this application as the project for the course.</p>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="entrepreneurship" /><category term="12 Startups" /><summary type="html"><![CDATA[City walks list many walks, trek, hike available near you in order to breath after this quarantine time.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/project-5-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/project-5-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Project 4 : Clear Camera for OBS</title><link href="https://cafournier.com/project-4-clear-camera" rel="alternate" type="text/html" title="Project 4 : Clear Camera for OBS" /><published>2020-05-10T00:00:00+00:00</published><updated>2020-05-10T00:00:00+00:00</updated><id>https://cafournier.com/project-4-clear-camera</id><content type="html" xml:base="https://cafournier.com/project-4-clear-camera"><![CDATA[<blockquote>
  <p>With Clear Camera, you can turn your iPhone or iPad into a powerful camera for your live streams.</p>
</blockquote>

<p>Clear camera is the fourth project of my <a href="https://cafournier.com/12-startups-challenge">12 startup challenge</a>.</p>

<h2 id="story-behind-this-project">Story behind this project</h2>

<p>During this quarantine, I noticed that a lot of people are discovering Twitch and among them some wanted to start streaming. That’s also true for me, this month I’ve wanted to start over DJaying and sometime launch a quick music live stream. In order to get a decent stream, you need a camera. I have my Macbook’s webcam that is not good at all. I have a great DSLR but I don’t have a video capture card. My last option is my iPhone, the camera is pretty good with the right light condition and the setup easy.</p>

<p>On mac you can easily capture the iPhone screen using Quicktime Player or OBS, from there you only need to put your iphone on a tripod and launch the camera. Since the entire screen of the phone is recorded you have all the UI displayed on your camera, the shutter button, video mode etc.</p>

<p>The idea was to create an app that display the camera preview without any unnecessary thing on the screen. After a quick prototype on Sketch the app only contains two buttons (camera switch, settings). Other actions are available with gestures.</p>

<p>When I submited the app to the App Store, I got a rejection because they found the app useless. So I thought on an other feature to add. Many streamers use an overlay around there webcam, this allows to get clean borders and sometimes displaying stream alerts like subscribtion, follow etc.</p>

<p>I designed several overlays on Sketch, with color variation. I added them to the app above the camera preview, you can change them by swipping left or right. I submitted the app again, and the app was accepted without any troubles.</p>

<h2 id="next-steps">Next steps</h2>

<p>The TODO list for this app is quite big, since I started late to work on this app. The main things are designing and adding more overlays, the possibility to import custom overlay, make use of the new API on iOS 13 to display both cameras (front and rear) on the screen and the last but not least adding face filters as you can find on Instagram and Snapshat. I also want to improve the App Store page of the app, I’m not really proud of what I’ve done and I’m sure I can improve this.</p>

<h2 id="useful-links">Useful links</h2>

<p><a href="https://apps.apple.com/us/app/clear-camera-pour-obs/id1510603713">Clear Camera on the iOS AppStore</a></p>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="entrepreneurship" /><category term="12 Startups" /><summary type="html"><![CDATA[With Clear Camera, you can turn your iPhone or iPad into a powerful camera for your live streams.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/project-4-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/project-4-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Project 3 : Beauty Mate</title><link href="https://cafournier.com/project-3-beauty-mate" rel="alternate" type="text/html" title="Project 3 : Beauty Mate" /><published>2020-04-13T00:00:00+00:00</published><updated>2020-04-13T00:00:00+00:00</updated><id>https://cafournier.com/project-3-beauty-mate</id><content type="html" xml:base="https://cafournier.com/project-3-beauty-mate"><![CDATA[<p>Beauty Mate is the third project of my <a href="https://cafournier.com/12-startups-challenge">12 startup challenge</a> and also the first fail of this challenge. I worked on this project during march.</p>

<h2 id="story-behind-this-project">Story behind this project</h2>

<p>For this project I’ve wanted to reuse the <a href="http://cafournier.com/project-2-fridge-buddy">Fridge Buddy</a> base code and adapt it to another domain, cosmetics. The idea was to build an inventory of all the beauty / cosmetics products you have at home. It can help when your on of your products is empty and you want to purchase a new one, you got the exact reference in your pocket at anytime.</p>

<p>The goal was to add new features on Beauty Mate and applying them also on Fridge Buddy, since the apps share the same base code. I thought it would be easy, in fact it was not that easy, because the code was duplicated in the two projects and not in a shared framework.</p>

<p>I also loosed interest for the subject and preferred working on improving directly Fridge Buddy since users were asking new features.</p>

<p>Lesson learned, for the next months I will focus on subjects that interest me.</p>

<h2 id="next-steps">Next steps</h2>

<p>Nothing since the app is not complete and I won’t work anymore on this project.</p>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="entrepreneurship" /><category term="12 Startups" /><summary type="html"><![CDATA[Beauty Mate is the third project of my 12 startup challenge and also the first fail of this challenge. I worked on this project during march.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/project-3-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/project-3-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Project 2 : Fridge Buddy - Expiration date tracking</title><link href="https://cafournier.com/project-2-fridge-buddy" rel="alternate" type="text/html" title="Project 2 : Fridge Buddy - Expiration date tracking" /><published>2020-04-08T00:00:00+00:00</published><updated>2020-04-08T00:00:00+00:00</updated><id>https://cafournier.com/project-2-fridge-buddy</id><content type="html" xml:base="https://cafournier.com/project-2-fridge-buddy"><![CDATA[<blockquote>
  <p>Fridge Buddy helps you reduce your food waste by alerting you when one of your products is nearing its expiration date.</p>
</blockquote>

<p><strong>Update 2026:</strong> FridgeBuddy has grown a lot since this post — household sharing, expiration-date scanning, widgets, and waste insights. See the current <a href="/fridgebuddy/">FridgeBuddy page</a> for what it does today.</p>

<p>Fridge Buddy is the second project of my <a href="https://cafournier.com/12-startups-challenge">12 startup challenge</a>. The blog post’s is a bit late, I worked on this project for the whole month of february.</p>

<h2 id="story-behind-this-project">Story behind this project</h2>

<p>The idea came when I had to trash some food due to the expiration date passed. I looked on the App Store to find an app that will helps me managing my groceries and avoid trashing my food. I found few apps that does what I want, add products and send notification when the expiration date is near. I tried them, but there were always something that I didn’t like so I decided to make my own version.</p>

<p>For this project I’ve wanted to follow the <a href="https://developer.apple.com/design/human-interface-guidelines/">Apple Human Interface Guideline</a> as much as possible. The second main goal is to be able to add a product quickly with few manual actions.</p>

<p>With these two objectives, I started designing the app on Sketch. It’s difficult for me to keep a sketch file organised, and this project is not an exception.</p>

<p>In order to add a product quickly, I’ve wanted to use the barcode to get all the product metadata. After searching a bit, I found <a href="https://world.openfoodfacts.org">OpenFoodFacts</a> that match all my needs. I decided to try the new framework <a href="https://developer.apple.com/documentation/combine">Combine</a> for all the network layer of the app. Since the network layer is light, currently the app is making only one call to the API, it’s a good stating point for using Combine. Regarding the notifications, they are only local notifications scheduled when you scan a new product, the app does not rely on third party service for the notifications.</p>

<p>I choosed a freemium business model, the core feature of the app is 100% free. You can scan unlimited products and get notifications for them. There are some small features behind the paywall, creating custom lists, change the sorting settings of the list. The in-app purchases are mainly a tip if you liked the app wanted to support and say thanks.</p>

<h2 id="next-steps">Next steps</h2>

<p>Since the app already got some users, I manage to get some feedback and to get an idea of the next features the users wants. I will implement them during the next months when I found a bit of time between the other projects.</p>

<h2 id="useful-links">Useful links</h2>

<p><a href="/fridgebuddy/">FridgeBuddy website</a></p>

<p><a href="https://apps.apple.com/app/apple-store/id1500190823?pt=2162580&amp;ct=cafournier&amp;mt=8">FridgeBuddy on the iOS AppStore</a></p>]]></content><author><name>Charles-Adrien Fournier</name></author><category term="entrepreneurship" /><category term="12 Startups" /><summary type="html"><![CDATA[Fridge Buddy helps you reduce your food waste by alerting you when one of your products is nearing its expiration date.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://cafournier.com/assets/images/posts/2020/project-2-thumbnail.jpg" /><media:content medium="image" url="https://cafournier.com/assets/images/posts/2020/project-2-thumbnail.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>