<?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: Karol Wrótniak</title>
    <description>The latest articles on DEV Community by Karol Wrótniak (@koral).</description>
    <link>https://dev.to/koral</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F436368%2Fc7788f68-8ad1-4589-8dc5-a4abc12fca33.jpeg</url>
      <title>DEV Community: Karol Wrótniak</title>
      <link>https://dev.to/koral</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koral"/>
    <language>en</language>
    <item>
      <title>When Zero‑Width Isn’t Zero: How I Found and Fixed a Vulnerability</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Wed, 28 Jan 2026 16:01:56 +0000</pubDate>
      <link>https://dev.to/gdg/when-zero-width-isnt-zero-how-i-found-and-fixed-a-vulnerability-16hi</link>
      <guid>https://dev.to/gdg/when-zero-width-isnt-zero-how-i-found-and-fixed-a-vulnerability-16hi</guid>
      <description>&lt;p&gt;When you set a max length on a form field or API, you expect it to hold. But what if a four-character string could secretly carry 10,000 extra bytes of invisible data, crashing your database or bypassing your validation? That was the vulnerability I found and fixed in the popular JavaScript library validator. It was a subtle bug involving Unicode Variation Selectors that allowed attackers to inject massive payloads while still passing length checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The isLength function is simple on the surface: count characters and compare to a limit. But in a Unicode world, “character” is tricky. You need to match the perceived length (what the user sees) with the storage length (what your database handles), or you risk truncation, performance issues, and, as I found, critical bypasses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unicode and the illusion of length
&lt;/h2&gt;

&lt;p&gt;JavaScript strings use &lt;a href="https://pl.wikipedia.org/wiki/UTF-16" rel="noopener noreferrer"&gt;UTF‑16&lt;/a&gt;. Some visible “characters” span two code units (surrogate pairs). isLength adjusts for that. It treats a base character plus a &lt;a href="https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)" rel="noopener noreferrer"&gt;Variation Selector&lt;/a&gt; as one perceived character. That’s because the selector only changes the presentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Emoji still count as one perceived character&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;smile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;smile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// 2 (UTF-16 code units)&lt;/span&gt;
&lt;span class="c1"&gt;// With isLength, it's treated as 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The attack vector: Variation selectors gone wild
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jeffkreeftmeijer.com/unicode-variation-selectors/" rel="noopener noreferrer"&gt;Variation Selectors&lt;/a&gt; (&lt;code&gt;U+FE0F&lt;/code&gt;, &lt;code&gt;U+FE0E&lt;/code&gt;) tweak how a base character displays. They aren’t content on their own. The old logic subtracted all selectors. So one can pad a short string with thousands of them while still passing the check with a low max.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Apparent 4 chars, but thousands of extra selectors&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;uFE0F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10,004 (UTF-16)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What are unicode variation selectors?
&lt;/h2&gt;

&lt;p&gt;These are zero‑width code points (U+FE0E for text and U+FE0F for emoji among the others) that modify the presentation of the character that immediately precedes them. They change appearance, not meaning. A base character plus a selector is meant to count as one perceived character.&lt;/p&gt;

&lt;p&gt;As of &lt;a href="https://www.unicode.org/versions/Unicode17.0.0/" rel="noopener noreferrer"&gt;Unicode 17.0&lt;/a&gt;, using them to choose emoji vs. text for many legacy &lt;a href="https://en.wikipedia.org/wiki/Dingbat" rel="noopener noreferrer"&gt;dingbat&lt;/a&gt; bases is being phased out. The new emojis have separate code points assigned for each style. For example, the emoji 🪯 (U+1FAAF KHANDA) has a dedicated emoji code point. The older dingbat-style symbol is ☬ (U+262C ADI SHAKTI). Variation selectors still exist and work for bases that support them. But modern additions increasingly prefer distinct code points over selector‑based presentation. Unicode provides the &lt;a href="https://www.unicode.org/emoji/charts/emoji-variants.html" rel="noopener noreferrer"&gt;emoji presentation sequences&lt;/a&gt; chart.&lt;/p&gt;

&lt;p&gt;Here are some examples (text vs. emoji):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Heart: ❤︎ (text, U+2764 U+FE0E) vs ❤️ (emoji, U+2764 U+FE0F)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Airplane: ✈︎ (text, U+2708 U+FE0E) vs ✈️ (emoji, U+2708 U+FE0F)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Snowman: ☃︎ (text, U+2603 U+FE0E) vs ☃️ (emoji, U+2603 U+FE0F)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing hand: ✍︎ (text, U+270D U+FE0E) vs ✍️ (emoji, U+270D U+FE0F)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Telephone: ☎︎ (text, U+260E U+FE0E) vs ☎️ (emoji, U+260E U+FE0F)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Console example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base character: Heavy Black Heart&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u2764&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heartText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;heart&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;uFE0E&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// request text style&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heartEmoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;heart&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;uFE0F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// request emoji style&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heartText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heartEmoji&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//  ❤︎ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, (with no selector), presentation differs by platform, browser, and font. Some show emoji by default; others prefer text. If you need a specific look, add U+FE0E (text) or U+FE0F (emoji). On the web, you can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-variant-emoji" rel="noopener noreferrer"&gt;font-variant-emoji&lt;/a&gt; CSS property.&lt;/p&gt;

&lt;p&gt;Important: A selector only makes sense right after a compatible base. Multiple selectors don’t stack or change meaning. ❤︎\uFE0F\uFE0F doesn’t become “more emoji.” Only the first base+selector pair affects the presentation. All the extras are stray code points.&lt;/p&gt;

&lt;p&gt;For validation, ignore only one base+selector pair. Count stray or repeated selectors toward length. Otherwise, a tiny word can hide kilobytes and waste CPU. You can find more information about the issue in the Snyk Vulnerability database under &lt;a href="https://security.snyk.io/vuln/SNYK-JS-VALIDATOR-13653476" rel="noopener noreferrer"&gt;SNYK-JS-VALIDATOR-13653476&lt;/a&gt; entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surgical fix: Counting only valid pairs
&lt;/h2&gt;

&lt;p&gt;The change is precise: subtract only base+selector pairs instead of every selector. Old: &lt;code&gt;/(\uFE0F|\uFE0E)/g&lt;/code&gt;. New: &lt;code&gt;/[^\uFE0F\uFE0E][\uFE0F\uFE0E]/g&lt;/code&gt;. &lt;code&gt;[^…]&lt;/code&gt; means “not these,” which forces a preceding non‑selector base. So stray or repeated selectors get counted. This distinction is the key to the fix.&lt;/p&gt;

&lt;p&gt;Effective length: &lt;code&gt;str.length — surrogatePairs — basePlusSelectorPairs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Padding with stray selectors now fails the max check&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;uFE0F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isLength(payload, { max: 4 }):&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;isLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;

&lt;span class="c1"&gt;// A valid base+selector pair still counts as one perceived char&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;basePair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;uFE0F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isLength(basePair, { max: 1 }):&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;isLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;basePair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Disclosure and timeline
&lt;/h2&gt;

&lt;p&gt;I followed a responsible disclosure process. I reported the issue, prepared a minimal PoC, and worked with the maintainers on a fix. The maintainers merged the patch, published a release, and then the advisory went live.&lt;/p&gt;

&lt;p&gt;I reported the issue along with a &lt;a href="https://github.com/validatorjs/validator.js/pull/2616" rel="noopener noreferrer"&gt;proposed fix&lt;/a&gt; on October 18, 2025. The maintainers merged a pull request on November 5. Finally, &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-12758" rel="noopener noreferrer"&gt;CVE-2025–12758&lt;/a&gt; was published on November 26. More info and links on the &lt;a href="https://github.com/advisories/GHSA-vghf-hv5q-vc2g" rel="noopener noreferrer"&gt;GitHub Security Advisory&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The key takeaway remains: Variation Selectors are modifiers, not content. The bug allowed them to be used as invisible padding to bypass max-length checks. After the fix only valid base-and-selector pairs pass the length check. It ensures all stray or repeated selectors are counted toward the actual length.&lt;/p&gt;

&lt;p&gt;Please update to &lt;code&gt;validator@13.15.22&lt;/code&gt; or newer. When working with string length, always measure what you are storing, not just what users see.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>javascript</category>
      <category>node</category>
      <category>vulnerabilities</category>
    </item>
    <item>
      <title>A Practical Guide to Flutter Accessibility - Part 1: The Basics</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Fri, 07 Nov 2025 12:08:20 +0000</pubDate>
      <link>https://dev.to/gdg/a-practical-guide-to-flutter-accessibility-part-1-the-basics-4bf1</link>
      <guid>https://dev.to/gdg/a-practical-guide-to-flutter-accessibility-part-1-the-basics-4bf1</guid>
      <description>&lt;p&gt;Learn how to build accessible Flutter apps using built-in tools. Fix common issues like screen reader incompatibility, confusing structure, and unclear state changes to create inclusive, user-friendly apps.&lt;/p&gt;

&lt;p&gt;Building accessible Flutter apps isn’t as complicated as you might think. Most accessibility problems happen because screen readers can’t understand your controls. Your information structure confuses users. You’re not communicating state changes. This guide will fix these problems using Flutter’s built-in accessibility tools.&lt;/p&gt;

&lt;p&gt;You’ll start by making silent controls speak to screen readers with proper labels and hints. Assistive technology users will get the same information as everyone else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters to you as a developer
&lt;/h3&gt;

&lt;p&gt;Accessibility isn’t just about doing the right thing (though that matters too). It’s about building better software. When you add proper semantic information to your widgets, you help more than screen reader users. You create clearer code that’s easier to test, debug, and maintain. Many accessibility improvements make the experience better for everyone — better focus management, clearer state communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  The legal reality
&lt;/h3&gt;

&lt;p&gt;Let’s talk about the elephant in the room: legal requirements. The EU Accessibility Act requires digital services to be accessible by 2025. Fines reach up to 4% of annual revenue for non-compliance. In the US, the ADA doesn’t specify technical standards. Courts reference WCAG guidelines in lawsuits. Companies like Target, Netflix, and Domino’s have faced million-dollar settlements over inaccessible apps. This isn’t some distant possibility — it’s happening right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good news about common problems
&lt;/h2&gt;

&lt;p&gt;The most common Flutter accessibility problems are simple to fix. An IconButton without a label? Silent to screen readers. You’ll fix that using Flutter’s Semantics and MergeSemantics widgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Semantics widget
&lt;/h2&gt;

&lt;p&gt;Think of the Semantics widget as an invisible layer of sticky notes for assistive technology. You wrap your existing UI and provide properties. These describe what the thing is (label, role). They show what it displays (value). They explain what it’ll do (hint). They communicate what state it’s in (flags like selected, hidden, header, button, enabled, liveRegion). You can expose actions (onTap, onLongPress, onScroll) so screen reader users can interact with your interface. You can group multiple visual widgets into a single logical unit.&lt;/p&gt;

&lt;p&gt;You won’t use Semantics everywhere — Flutter’s smart about defaults for Material widgets. But you’ll add or override it when something’s silent, chatty, or missing important state information. The goal? Give just enough structured metadata so a user hears “Add to cart, button, selected” instead of a confusing jumble of unrelated text bits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving a voice to silent icons
&lt;/h2&gt;

&lt;p&gt;Here’s our first problem. You’ve got an IconButton that looks fine. For a screen reader, it’s a silent mystery. Wrap it with Semantics and give it a clear label (what it is) and optional hint (what happens when you tap it).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without Semantics: announces only "button" because the icon has no text.&lt;/span&gt;
&lt;span class="n"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;local_drink&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_incrementCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Logs a glass but screen reader doesn't know.&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// With Semantics: announces "Log water, button" then the hint on explore.&lt;/span&gt;
&lt;span class="n"&gt;Semantics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'Log water'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// What this control represents&lt;/span&gt;
  &lt;span class="nl"&gt;hint:&lt;/span&gt; &lt;span class="s"&gt;'Adds one glass to today&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s total'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// What happens on activation.&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plus_one&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_incrementCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep labels short and specific. Don’t repeat role words like “button” — the system adds that for you. Use value when the visual text alone would be confusing out of context (like an isolated number that means nothing without context).&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding other semantic properties
&lt;/h2&gt;

&lt;p&gt;Many other semantic properties solve specific accessibility problems. liveRegion makes dynamic content announce when it changes — perfect for status updates or form validation messages. The isHeader flag tells screen readers that text is a page or section heading. This helps users navigate by jumping between sections. We can’t cover every semantic property in one post. You’ll learn about these and others as we work through real examples. For the complete rundown, check out the &lt;a href="https://api.flutter.dev/flutter/widgets/Semantics-class.html" rel="noopener noreferrer"&gt;official Semantics documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform differences to watch
&lt;/h2&gt;

&lt;p&gt;Not all semantic properties work the same way across platforms. Some platforms support certain properties. Others might behave differently. For example, headingLevel only works on Flutter web. Native iOS supports heading levels. Flutter doesn’t wire this through on iOS (&lt;a href="https://github.com/flutter/flutter/issues/155928" rel="noopener noreferrer"&gt;there’s an open issue&lt;/a&gt; in the Flutter tracker). Android doesn’t even have the concept of heading levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from Android development
&lt;/h2&gt;

&lt;p&gt;Do you use Jetpack Compose? Flutter’s Semantics widget works like Compose’s semantics modifier. Both use the same basic idea — wrap UI elements and add metadata like contentDescription (Flutter’s label), role information, and state flags. The main difference is syntax. Flutter uses named parameters in a widget wrapper. Compose uses a modifier with lambda configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from iOS development
&lt;/h2&gt;

&lt;p&gt;SwiftUI’s accessibility system maps to Flutter’s approach. Flutter’s label property is like SwiftUI’s .accessibilityLabel() modifier. hint maps to .accessibilityHint(). State flags like isHeader correspond to .accessibilityAddTraits(.isHeader). All three frameworks — Flutter, &lt;a href="https://developer.android.com/jetpack/compose/semantics" rel="noopener noreferrer"&gt;Jetpack Compose&lt;/a&gt;, and &lt;a href="https://developer.apple.com/documentation/swiftui/view-accessibility" rel="noopener noreferrer"&gt;SwiftUI&lt;/a&gt; — follow the same core principle: decorate UI elements with semantic metadata. Once you get the concept, switching between platforms becomes easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing your semantics on device
&lt;/h2&gt;

&lt;p&gt;Let’s see what happens when you run the IconButton example on an Android device with TalkBack enabled. The screen recording below shows the app running on an emulator and the Android Ally plugin in Android Studio. This gives you a real-time control of accessibility settings.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1134569751" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Android emulator with TalkBack and Android Ally plugin.&lt;/p&gt;
&lt;h2&gt;
  
  
  The double announcement problem
&lt;/h2&gt;

&lt;p&gt;When you swipe through the interface with TalkBack (Android’s built-in screen reader), you’ll notice something weird. Both the Semantics wrapper and the underlying IconButton get announced as separate nodes. TalkBack first reads “Log water, button, Adds one glass to today’s total” from your custom semantics. Then it announces the icon button itself as a separate focusable element.&lt;/p&gt;

&lt;p&gt;This creates a confusing experience for screen reader users. They hear the same control twice but with different information. The &lt;a href="https://plugins.jetbrains.com/plugin/7299-android-ally" rel="noopener noreferrer"&gt;Android Ally plugin&lt;/a&gt; helps you catch these issues. It shows the accessibility tree structure alongside your app. You can grab it from Android Studio’s plugin marketplace to see how assistive technologies interpret your interface.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using Accessibility Scanner
&lt;/h2&gt;

&lt;p&gt;Google’s Accessibility Scanner app catches this exact problem. The scanner analyzes your app’s interface and flags accessibility issues. It finds duplicate content descriptions, missing labels, and poor contrast ratios. When you run it on our IconButton example, it spots the redundant semantic information. It suggests consolidating everything into a single, clear description.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feabeixfbnyh5et559yo9.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feabeixfbnyh5et559yo9.jpeg" alt="Accessibility scanner detecting duplicate content descriptions." width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can download &lt;a href="https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor" rel="noopener noreferrer"&gt;Accessibility Scanner&lt;/a&gt; from the Google Play Store. Run it on your device or emulator. It’s useful during development because it gives you the same feedback that real users with disabilities would get. You don’t have to enable TalkBack yourself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing on iOS
&lt;/h2&gt;

&lt;p&gt;On iOS, Apple’s &lt;a href="https://developer.apple.com/documentation/accessibility/accessibility-inspector" rel="noopener noreferrer"&gt;Accessibility Inspector&lt;/a&gt; does the same thing. This built-in developer tool comes with Xcode. It lets you inspect the accessibility tree of your Flutter app running on iOS Simulator or a physical device. When you run the inspector on our problematic IconButton example, it reveals the same issue. You see duplicate semantic nodes that would confuse VoiceOver users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax88h9qz75ie8d6ttigp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax88h9qz75ie8d6ttigp.jpeg" alt="Apple Accessibility Inspector showing duplicate semantic elements." width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the Accessibility Inspector in Xcode under Developer Tools. Or launch it from your Applications/Utilities folder if you’ve got Command Line Tools installed. Unlike the Android tools, Accessibility Inspector works with Flutter apps in iOS Simulator. This makes it easy to catch accessibility issues during development without needing a physical device.&lt;/p&gt;

&lt;p&gt;Why does this double announcement happen? You wrapped the IconButton without telling Flutter to treat them as a single semantic unit. Let’s fix this by learning how to group related elements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing double announcements with MergeSemantics
&lt;/h2&gt;

&lt;p&gt;The solution to our double announcement problem is simpler than you might expect. We need to tell Flutter to merge the semantic information from our custom Semantics wrapper with the underlying IconButton. This creates a single focusable element. The MergeSemantics widget does this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fixed: Now announces as a single element.&lt;/span&gt;
&lt;span class="n"&gt;MergeSemantics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Semantics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;label:&lt;/span&gt; &lt;span class="s"&gt;'Log a glass of water'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     
    &lt;span class="nl"&gt;hint:&lt;/span&gt; &lt;span class="s"&gt;'Adds one glass to today&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;s total'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plus_one&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_incrementCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you wrap multiple widgets with MergeSemantics, Flutter combines all their semantic properties into a single accessibility node. Screen readers now announce “Log a glass of water, button, Adds one glass to today’s total” as one cohesive unit. They don’t treat each wrapper as a separate element.&lt;/p&gt;

&lt;p&gt;The MergeSemantics widget is handy when you’ve got compound controls. Think of a button with an icon and text. Or a custom card with multiple interactive elements that should be treated as one logical unit. Without it, screen readers navigate to each semantic layer. This creates confusion for users who expect related elements to be grouped together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-platform grouping concepts
&lt;/h2&gt;

&lt;p&gt;If you’re working with SwiftUI, this concept maps to the .accessibilityElement(children: .combine) view modifier. This merges accessibility information from child views into a single element. In Jetpack Compose, you get grouping using the mergeDescendants = true parameter in the semantics modifier. All three frameworks recognize that visual hierarchy doesn’t match logical accessibility structure. Multiple UI elements should be announced as one cohesive unit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the fixed implementation
&lt;/h2&gt;

&lt;p&gt;Now when you test the corrected code with TalkBack or VoiceOver, you’ll hear a single, clear announcement. No more confusing double reading we had before. The screen recording below shows the same setup as earlier. The app runs with both the Android Ally plugin and Accessibility Scanner. This time it displays the clean, merged semantic structure.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1134570595" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;br&gt;
MergeSemantics fixing the double announcement issue.&lt;/p&gt;

&lt;p&gt;Notice how the accessibility tree now shows just one focusable element instead of those duplicate nodes you saw earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirming results on iOS
&lt;/h2&gt;

&lt;p&gt;On iOS, the Accessibility Inspector confirms the same clean result. When you run the corrected MergeSemantics code through Apple’s accessibility tools, the inspector shows a single, merged semantic node. No duplicate announcements or structural issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv50d22dazdbjxuiugof7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv50d22dazdbjxuiugof7.jpeg" alt="iOS Accessibility Inspector showing the clean, merged semantic structure." width="800" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both TalkBack and the Accessibility Scanner confirm that those redundant announcements are gone. This creates a much better experience for screen reader users.&lt;/p&gt;

&lt;p&gt;For more details on semantic grouping options, check out the &lt;a href="https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html" rel="noopener noreferrer"&gt;official MergeSemantics documentation&lt;/a&gt; and the broader &lt;a href="https://api.flutter.dev/flutter/widgets/Semantics-class.html" rel="noopener noreferrer"&gt;Semantics class reference&lt;/a&gt;. These cover all available properties and grouping strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you’ve accomplished
&lt;/h2&gt;

&lt;p&gt;You’ve now tackled the most common Flutter accessibility problems using the core toolkit. Silent controls like IconButton now speak with proper labels and hints. Those confusing double announcements? Cleaned up with MergeSemantics. Your app’s semantic structure makes sense to screen readers. You’ve got the tools to test and verify your changes work across both Android and iOS.&lt;/p&gt;

&lt;p&gt;These basic semantic properties — solve the vast majority of the accessibility issues you’ll run into in Flutter apps. But there’s more to building inclusive experiences. In Part 2 of this series, you’ll dive into advanced interaction patterns like custom semantic actions for complex gestures.You’ll explore how to handle dynamic content announcements and make custom widgets accessible to assistive technologies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.thedroidsonroids.com/blog/flutter-accessibility-guide-part-1" rel="noopener noreferrer"&gt;&lt;em&gt;thedroidsonroids.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on November 7, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>flutter</category>
      <category>ios</category>
      <category>a11y</category>
    </item>
    <item>
      <title>How to Create an IoT App in Kotlin Multiplatform</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Sun, 05 Oct 2025 22:45:33 +0000</pubDate>
      <link>https://dev.to/koral/how-to-create-an-iot-app-in-kotlin-multiplatform-204m</link>
      <guid>https://dev.to/koral/how-to-create-an-iot-app-in-kotlin-multiplatform-204m</guid>
      <description>&lt;p&gt;In this article, you’ll build a small multiplatform app for Android and iOS that blinks an LED on an IoT device. You’ll use Kotlin Multiplatform (KMP) for shared logic, Compose Multiplatform for the UI, and the &lt;a href="https://github.com/JuulLabs/kable" rel="noopener noreferrer"&gt;Kable&lt;/a&gt; library for Bluetooth communication.&lt;/p&gt;

&lt;p&gt;Kotlin Multiplatform provides the foundation for sharing application logic. Compose Multiplatform lets you create a fluid, native UI for both Android and iOS from a single codebase. For connectivity, you'll use Kable, a modern, coroutine-based library that simplifies Bluetooth Low Energy (BLE) communication. This stack helps you build high-performance, native IoT controller apps, reuse the most code, and improve your development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you'll create a simple IoT controller app for Android and iOS called "Bluno Blink Controller." The app will connect to a Bluno BLE device, let you select a number on a slider, and send it to the device. The Bluno will then blink its onboard LED the corresponding number of times. This project shows how to interact with hardware from a shared codebase.&lt;/p&gt;

&lt;p&gt;The video below shows the final app controlling the Bluno device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb92pdea6sd9p7l802ror.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb92pdea6sd9p7l802ror.gif" alt="Bluno blink controller in action" width="320" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bluno blink controller in action&lt;/p&gt;

&lt;p&gt;And here's a video showing Bluno devices in action, blinking in response to commands from the app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmepz4734e6u76j9o1a1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmepz4734e6u76j9o1a1.gif" alt="Bluno device blinking" width="320" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bluno device blinking&lt;/p&gt;

&lt;p&gt;The main reason to choose Kotlin Multiplatform for an IoT project is code sharing. IoT apps often have identical logic across platforms for managing connection states, defining communication protocols, and processing data. KMP lets you write this core logic once in a shared module, which reduces duplication and errors. This approach gives you a single source of truth for your app's logic while delivering a native experience on both Android and iOS.&lt;/p&gt;

&lt;p&gt;While KMP is great for shared logic, you still need to handle platform-specific tasks like runtime permissions and enabling the Bluetooth adapter or location services. You do this in the native Android and iOS source sets. Once you complete the platform-specific setup, a library like Kable can manage BLE communication. Kable provides a unified, cross-platform abstraction for scanning, connecting, and exchanging data with BLE devices, which you'll use in the shared code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The atack at a glance
&lt;/h2&gt;

&lt;p&gt;You'll build the project on a stack where each layer has a clear role, from the user interface down to the hardware. The diagram below shows how these components fit together, with a shared core running on both Android and iOS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxvhjk0g4ob0lpbuygm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxvhjk0g4ob0lpbuygm4.png" alt="Stack diagram for an IoT app in Kotlin Multiplatform" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Architecture diagram&lt;/p&gt;

&lt;p&gt;Here’s a closer look at each component's role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kotlin Multiplatform:&lt;/strong&gt; This is the core of your project. It lets you share the application's business logic between Android and iOS. You'll write all logic for managing connection state, handling BLE events, and preparing data for the Bluno device once in the commonMain source set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compose Multiplatform:&lt;/strong&gt; You'll use Compose Multiplatform to build the entire UI from a single, shared codebase. The screen contains a connection button, status indicators, and a blink-count slider. You only need to define it once with composable functions that render natively on both Android and iOS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kable:&lt;/strong&gt; This coroutine-based library is your tool for Bluetooth Low Energy communication. It provides a clean, modern API for scanning peripherals, establishing connections, and reading or writing data to device characteristics. Kable abstracts many platform-specific BLE complexities, letting you write the communication logic once in shared Kotlin code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bluno BLE Device:&lt;/strong&gt; Your IoT app targets a &lt;a href="https://www.dfrobot.com/product-1044.html" rel="noopener noreferrer"&gt;Bluno&lt;/a&gt; device, an &lt;a href="https://www.arduino.cc/" rel="noopener noreferrer"&gt;Arduino&lt;/a&gt;-compatible board with a BLE module.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Meet the hardware: The Bluno BLE device
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, it helps to understand the hardware you'll control. The Bluno is a microcontroller board based on the Arduino platform. An Arduino is a tiny, programmable computer that interacts with the physical world through sensors, motors, and LEDs. Hobbyists and professionals use it to build interactive electronic projects.&lt;/p&gt;

&lt;p&gt;The Bluno is special because it has a built-in Bluetooth Low Energy (BLE) module. This lets it communicate wirelessly with devices like smartphones, making it a great choice for simple IoT apps. The logic on the Bluno runs a C++-based language, and you develop it using an environment like the &lt;a href="https://www.arduino.cc/en/software/" rel="noopener noreferrer"&gt;Arduino IDE&lt;/a&gt;. For this project, the device runs a simple script (in arduino/BlunoBlinkController.ino) that listens for a number sent over BLE and blinks an LED accordingly.&lt;/p&gt;

&lt;p&gt;This development model is different from mobile app development. Instead of an event-driven UI, Arduino programs run a continuous loop() function that contains the device's main logic. You also work with significant constraints. A microcontroller has a fraction of the memory and processing power of a modern smartphone, so the code must be efficient. Communication happens through a &lt;a href="https://docs.arduino.cc/language-reference/en/functions/communication/serial/" rel="noopener noreferrer"&gt;Serial&lt;/a&gt; interface. On a standard Arduino, this would be a USB cable. On the Bluno, the firmware bridges this Serial communication over its BLE connection, letting your mobile app send and receive data like a wired terminal.&lt;/p&gt;

&lt;p&gt;For your KMP app to communicate with the Bluno, it needs the device's specific BLE "address." This isn't a physical address but a set of unique identifiers (UUIDs) for the services and characteristics the device exposes. A "service" is a collection of related functions, and a "characteristic" is a piece of data within that service. In our case, the Bluno exposes a serial port service that we can write to.&lt;/p&gt;

&lt;p&gt;Here is the specific characteristic our app will target, defined in the shared Kotlin code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/Constants.kt&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;blunoSerialPortCharacteristic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;characteristicOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0000dfb0-0000-1000-8000-00805f9b34fb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;characteristic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0000dfb1-0000-1000-8000-00805f9b34fb"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you send the blink count from the app, you write to this exact characteristic. The Bluno's firmware listens for this write event, reads the number, and runs the blinking sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;The project uses a standard Kotlin Multiplatform structure. The core logic and UI are in the composeApp module. The composeApp/build.gradle.kts file configures key dependencies, including Compose Multiplatform for the UI and Kable for BLE communication.&lt;/p&gt;

&lt;p&gt;The most KMP-specific part of the configuration is the kotlin block, where you declare compilation targets. This project targets Android and multiple iOS architectures to support physical devices and simulators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;androidTarget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;iosX64&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;iosArm64&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;iosSimulatorArm64&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;iosTarget&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;iosTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;framework&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ComposeApp"&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;isStatic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration tells Kotlin to build a native Android app and an iOS framework from the same shared codebase.&lt;/p&gt;

&lt;p&gt;Two important settings here are baseName and isStatic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  baseName = "ComposeApp": This sets the name of the compiled framework. The iOS app will use this name in its Swift code (import ComposeApp) to access the shared Kotlin code.&lt;/li&gt;
&lt;li&gt;  isStatic = true: This configures the framework as a static library. The compiled Kotlin code is linked directly into the final app executable at build time. This is a common way to integrate KMP into an iOS project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BLE basics &amp;amp; the Kable Mental Model
&lt;/h2&gt;

&lt;p&gt;At its core, BLE communication uses a client-server model. The peripheral device (your Bluno) is the server, which advertises its presence and data. The central device (your phone) is the client, which scans for and consumes that data. The peripheral exposes its features through a standard structure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Peripheral:&lt;/strong&gt; The IoT device itself (the Bluno).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Service:&lt;/strong&gt; A collection of related functions. Our Bluno has a "Serial Port" service.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Characteristic:&lt;/strong&gt; A specific piece of data within a service. A characteristic isn't just a value; it has properties that define how you can interact with it. You can &lt;strong&gt;write&lt;/strong&gt; to it (send data), &lt;strong&gt;read&lt;/strong&gt; from it (request its current value), or &lt;strong&gt;subscribe to notifications&lt;/strong&gt; to have the peripheral push updates to you automatically when the value changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kable provides a developer-friendly abstraction over this structure. It simplifies low-level platform details into a clean, coroutine-based API centered on the Peripheral object. This object represents the device you want to interact with.&lt;/p&gt;

&lt;p&gt;The typical workflow for connecting and sending data looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Scan:&lt;/strong&gt; Use a Scanner to find nearby devices. Kable lets you filter by advertising data, such as services, name, or manufacturer-specific data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Connect:&lt;/strong&gt; Once you find the target device's advertisement, you create a Peripheral instance from it and call connect().&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Interact (Read/Write/Observe):&lt;/strong&gt; After connecting, you can interact with the device's characteristics. Kable handles service discovery, so you can start making calls right away. You can write() to send data, read() for a one-time data request, or use observe() to listen for a stream of notifications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the project, BleRepository encapsulates this logic. The scanAndConnect function creates a Scanner that filters for Bluno's unique service UUID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/BleRepository.kt&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;scanner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Scanner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blunoSerialPortCharacteristic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serviceUuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;advertisement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;advertisements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Find the first matching device&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this tutorial, you connect to the first device that matches the service filter. In a production app, you would probably scan for a few seconds, show a list of found devices, and let the user choose one.&lt;/p&gt;

&lt;p&gt;Once the app finds the device, it creates a Peripheral and connects to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/BleRepository.kt&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;connectToAdvertisement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertisement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Advertisement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Peripheral&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;advertisement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_connectionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Connected&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_isReadyToSend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The peripheral.connect() call returns a CoroutineScope that remains active for the connection's duration. If the device disconnects, this scope cancels automatically, giving you a structured way to manage connection-specific tasks. The UI observes the connection status, which is updated in a StateFlow.&lt;/p&gt;

&lt;p&gt;The BleRepository uses two StateFlows to communicate its state to the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  connectionStatus: This flow tells the UI if the app is Idle, InProgress, or Connected. The UI uses this to change the button text (e.g., from "Scan &amp;amp; Connect" to "Disconnect"), show a progress indicator, and display controls only when connected.&lt;/li&gt;
&lt;li&gt;  isReadyToSend: This boolean flow controls the UI's interactivity. It enables the slider and "Send" button when the device is ready and disables them while a command is in flight. This prevents the user from sending multiple commands at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The BleViewModel exposes these flows, and the App composable collects them. This creates a reactive link between the back-end BLE logic and the front-end UI state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the "Blink Count" feature
&lt;/h2&gt;

&lt;p&gt;With the connection established, you can now implement the main feature: sending the blink count to Bluno. This process involves capturing user input, encoding it for BLE, writing it to the device, and waiting for a confirmation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. UI and state management
&lt;/h3&gt;

&lt;p&gt;The BlinkControls composable provides a Slider to select a number and a "Send" button. The screenshot below shows the app's main screen when connected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojugkhxnt7d906uuwdj1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojugkhxnt7d906uuwdj1.webp" alt="Blink Controller UI" width="270" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blink Controller UI&lt;/p&gt;

&lt;p&gt;When the user presses "Send," it triggers the onSend callback, passing the integer value to the BleViewModel. The ViewModel then calls the corresponding function in the BleRepository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/BleViewModel.kt&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendBlinkCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;bleRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendBlinkCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Sending data and awaiting confirmation
&lt;/h3&gt;

&lt;p&gt;The BleRepository handles the logic for sending data and managing UI state. When sendBlinkCount is called, it sets isReadyToSend to false. This disables the "Send" button to prevent the user from sending a new command while the first one is running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/BleRepository.kt&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendBlinkCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_isReadyToSend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;awaitBlinkConfirmation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_isReadyToSend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;writeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You launch two independent coroutines in the peripheral's connection scope. The first one listens for the device's confirmation signal. The second one sends the data. This separation is important because writing the command and listening for the response are distinct, asynchronous operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Writing to the characteristic
&lt;/h3&gt;

&lt;p&gt;The writeData function is where the app communicates with Bluno. It takes the integer, converts it to a ByteArray, and uses Kable's write function to send it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/BleRepository.kt&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;writeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;characteristic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blunoSerialPortCharacteristic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encodeToByteArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;writeType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WriteType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WithoutResponse&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you use WriteType.WithoutResponse, a "fire-and-forget" write. The app sends the data but doesn't wait for a low-level acknowledgment from the BLE stack. This is fine for our use case because we implement our own application-level confirmation by listening for the "C" character.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handling the device response
&lt;/h3&gt;

&lt;p&gt;After the Bluno receives the number, it blinks its LED and sends a confirmation character ("C") back to the app to signal it is finished. The awaitBlinkConfirmation function catches this response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// file: composeApp/src/commonMain/kotlin/pl/droidsonroids/kmpiotdemo/data/BleRepository.kt&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;awaitBlinkConfirmation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;peripheral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blunoSerialPortCharacteristic&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;awaitConfirmation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="nf"&gt;createPollingFlow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;awaitConfirmation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;awaitConfirmation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;decodeToString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BLINK_DONE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It first tries to observe() the characteristic, which sets up a notification listener. It then waits for a ByteArray that contains the "C" token when decoded. Once it receives this confirmation, the coroutine completes, isReadyToSend becomes true, and the UI becomes interactive again.&lt;/p&gt;

&lt;p&gt;The try-catch block handles a common hardware quirk. For BLE notifications to work, a characteristic must include a &lt;strong&gt;Client Characteristic Configuration Descriptor (CCCD)&lt;/strong&gt; that the client app writes to. The Bluno firmware, however, doesn't expose this descriptor correctly. This is a known issue, especially on iOS, where the Core Bluetooth framework strictly follows the BLE specification and refuses to subscribe to a characteristic without a valid CCCD. This causes Kable's observe() call to fail.&lt;/p&gt;

&lt;p&gt;To work around this hardware limitation, the code falls back to a manual polling flow. If the observe() call throws an IOException, the catch block repeatedly reads the characteristic's value until it receives the confirmation token. While less efficient than notifications, this polling strategy reliably gets the completion signal from this device. You can read more about this long-standing issue in &lt;a href="https://github.com/Jakeler/ble-serial/issues/57" rel="noopener noreferrer"&gt;this ble-serial GitHub thread&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling platform specifics
&lt;/h2&gt;

&lt;p&gt;While most of your code is in commonMain, you still need to write some platform-specific code in the native Android and iOS source sets. Here, you handle system-level features like permissions and services before the shared code takes over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android
&lt;/h3&gt;

&lt;p&gt;On Android, you must handle permissions, the Bluetooth adapter, and location services.&lt;/p&gt;

&lt;h4&gt;
  
  
  Android permissions
&lt;/h4&gt;

&lt;p&gt;First, you declare the necessary permissions in composeApp/src/androidMain/AndroidManifest.xml. For an app targeting SDK 31 (Android 12) or newer, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  BLUETOOTH_SCAN&lt;/li&gt;
&lt;li&gt;  BLUETOOTH_CONNECT&lt;/li&gt;
&lt;li&gt;  ACCESS_FINE_LOCATION&lt;/li&gt;
&lt;li&gt;  ACCESS_COARSE_LOCATION&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might wonder why location permissions are needed. On Android, discovering nearby BLE devices can infer the user's location. For this privacy reason, Google requires apps to have location permission to perform BLE scans. You cannot add the neverForLocation attribute to the scan permission for the same reason.&lt;/p&gt;

&lt;h4&gt;
  
  
  Android runtime checks
&lt;/h4&gt;

&lt;p&gt;In MainActivity.kt, you implement the runtime logic to ensure all requirements are met before the user can connect. The code does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Requests permissions:&lt;/strong&gt; It uses ActivityResultContracts to launch the standard runtime permission dialogs.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enables Bluetooth:&lt;/strong&gt; If the Bluetooth adapter is off, it launches an intent with BluetoothAdapter.ACTION_REQUEST_ENABLE to prompt the user to turn it on.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enables location:&lt;/strong&gt; It checks if Location Services are enabled and directs the user to system settings if they are not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The official Android documentation notes that intents for Bluetooth, location, and application settings may not always resolve. A device manufacturer might block or fail to implement them. The sample project handles this by falling back to the generic settings screen.&lt;/p&gt;

&lt;p&gt;To keep the UI state synchronized, the app rechecks all permissions and service statuses in the onResume lifecycle method. This ensures that if a user enables Bluetooth in system settings and returns to the app, the UI correctly reflects the new state. Additionally, a BroadcastReceiver listens for ACTION_STATE_CHANGED (for Bluetooth) and PROVIDERS_CHANGED_ACTION (for location). This allows the app to react to these service changes in real-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS
&lt;/h3&gt;

&lt;p&gt;On iOS, the platform handles more of the setup flow automatically, so you write less boilerplate code.&lt;/p&gt;

&lt;h4&gt;
  
  
  iOS permissions
&lt;/h4&gt;

&lt;p&gt;For permissions, you only need to provide a usage description string in the iosApp/iosApp/Info.plist file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- file: iosApp/iosApp/Info.plist --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSBluetoothAlwaysUsageDescription&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;This app uses Bluetooth to scan and connect to BLE devices&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time Kable attempts a BLE scan, iOS automatically detects the required permission, finds this string, and displays a standard system dialog.&lt;/p&gt;

&lt;h4&gt;
  
  
  iOS Bluetooth service
&lt;/h4&gt;

&lt;p&gt;The flow for enabling Bluetooth is also system-managed. If a user tries to use the app with Bluetooth off, the Core Bluetooth framework automatically displays a system alert prompting them to enable it. This differs from Android, where you must manually create and launch an intent for a similar prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Real-world IoT is rarely as clean as this sample app. Platform-specific quirks, where devices don't conform to standards, are just one part of the challenge. You will encounter issues that can affect reliability and user experience. Remember that Bluetooth can be unreliable: connections drop, devices lose power, or users might turn off their devices or Bluetooth.&lt;/p&gt;

&lt;p&gt;The current app's most critical flaw is its inability to handle unexpected disconnects. If the Bluno device loses power or goes out of range while the app waits for the blink confirmation, the UI will get stuck with a spinning progress indicator. Another issue is that the app will wait forever for a device during the initial scan. You can fix both problems by adding timeouts. After a reasonable period, such as 10 seconds, you can abort the scan or connection attempt and reset the UI to its Idle state, letting the user try again. For a better user experience, you could add a dedicated Error state to display a helpful message like "Device not found" instead of just returning to the idle screen.&lt;/p&gt;

&lt;p&gt;You should now have a solid understanding of how to build a simple IoT app with Kotlin Multiplatform. The worlds of IoT and Kotlin Multiplatform are evolving quickly, so stay updated with the latest library versions and best practices.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>ios</category>
      <category>iot</category>
    </item>
    <item>
      <title>Deep Diving Into AI_devs 3: What I Learned And How You Can Benefit</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Sat, 22 Feb 2025 12:20:34 +0000</pubDate>
      <link>https://dev.to/koral/deep-diving-into-aidevs-3-what-i-learned-and-how-you-can-benefit-5fe5</link>
      <guid>https://dev.to/koral/deep-diving-into-aidevs-3-what-i-learned-and-how-you-can-benefit-5fe5</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.aidevs.pl/" rel="noopener noreferrer"&gt;AI_devs 3&lt;/a&gt; course has provided an &lt;strong&gt;in-depth exploration of advanced concepts in &lt;a href="https://www.thedroidsonroids.com/blog/ai-in-app-development" rel="noopener noreferrer"&gt;artificial intelligence&lt;/a&gt;&lt;/strong&gt;. It focuses on the &lt;strong&gt;integration and application of LLMs&lt;/strong&gt; (Large Language Models) in real-world scenarios. This includes not only tools and techniques to enhance your understanding of AI development, but also data setup, working with LLMs, and building retrieval-augmented generation (RAG) systems. This article aims to summarize the key takeaways and insights gained from the course.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;strong&gt;The intention is purely to provide an unbiased, subjective opinion about the course.&lt;/strong&gt; &lt;strong&gt;this article is not affiliated in any way by the company behind AI_devs.&lt;/strong&gt; I received neither compensation nor a discount. You won’t find any referral links here. At the time of writing this article, the course is not purchasable. I receive no benefits from advertising or promoting AI_devs, or its authors. Moreover, I paid the same price for the course as everyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is the course organized?
&lt;/h2&gt;

&lt;p&gt;The main training consists of five episodes, each containing five lessons. Every lesson appeared on a consecutive workday, so the main course lasted five weeks in total. There was also an optional, introductory pre-work week.&lt;/p&gt;

&lt;p&gt;Almost every lesson ends with a task. &lt;strong&gt;You have to solve at least 80% of the tasks to get a certificate&lt;/strong&gt; (here is &lt;a href="https://credsverse.com/credentials/b852d146-0491-436a-8a19-dc68bb6e6d03" rel="noopener noreferrer"&gt;mine&lt;/a&gt;). Note that these tasks involve communication via API. The API keys were separate for each participant. You have to write code (or instruct some LLM to write it for you) and execute it to pass. Code samples provided in the lessons are in JavaScript but &lt;strong&gt;you can use any programming language you are familiar with.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There were also extra tasks, for fun, that did not count towards the certificate. All the tasks (both normal and extra ones) used the CTF (Capture The Flag) form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most important concepts
&lt;/h2&gt;

&lt;p&gt;Look at the course name suffix: &lt;strong&gt;agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means it teaches &lt;strong&gt;how to use programming tools to solve problems automatically&lt;/strong&gt;. Usually an agent uses many lower-level tools, such as some LLM via API and a database (not necessarily dedicated for AI).&lt;/p&gt;

&lt;p&gt;It is important to handle unhappy scenarios gracefully. &lt;strong&gt;Giving up and showing an error to the user is better than presenting an incorrect or off-topic answer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI tools for development
&lt;/h2&gt;

&lt;p&gt;There are a lot of tutorials and trainings on using LLMs and prompt engineering. In contrast, there is much less information about &lt;strong&gt;auxiliary utilities&lt;/strong&gt;. These include powerful tools for &lt;strong&gt;monitoring and debugging AI applications&lt;/strong&gt;, including specialized databases for vector storage, and utilities for web crawling. I’ll describe a few of them used in the course.&lt;/p&gt;

&lt;p&gt;Read also: &lt;a href="https://www.thedroidsonroids.com/blog/best-ai-coding-assistant-tools" rel="noopener noreferrer"&gt;10 Best AI Coding Assistant Tools in 2025 — Guide for Developers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FireCrawl: Web content extraction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.firecrawl.dev/" rel="noopener noreferrer"&gt;FireCrawl&lt;/a&gt; is a web scraper designed for AI applications. It focuses on &lt;strong&gt;extracting clean, structured content from web pages&lt;/strong&gt;. It can filter out noise like ads, navigation menus, and irrelevant elements. This makes it useful for feeding high-quality web content into LLM-powered applications. The tool can handle modern JavaScript-heavy websites and maintains proper content hierarchy. All of this makes it a powerful component for building AI agents that need to understand web content.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangFuse: Monitoring and debugging AI applications
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://langfuse.com/" rel="noopener noreferrer"&gt;LangFuse&lt;/a&gt; is a &lt;strong&gt;monitoring and debugging platform for LLM-powered applications&lt;/strong&gt;. It provides insights into token usage and costs. It can also analyze latency, and the performance of AI interactions. The platform allows debug prompts, and analyzes how they behave in production.&lt;/p&gt;

&lt;p&gt;There are other alternative tools for prompt debugging and analyzing. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://smith.langchain.com/" rel="noopener noreferrer"&gt;LangSmith&lt;/a&gt; — Developed by LangChain, offering comprehensive debugging and monitoring features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://portkey.ai/" rel="noopener noreferrer"&gt;Portkey&lt;/a&gt; — Focuses on prompt management and optimization with A/B testing capabilities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.parea.ai/" rel="noopener noreferrer"&gt;Parea&lt;/a&gt; — Provides analytics and monitoring with emphasis on prompt version control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.helicone.ai/" rel="noopener noreferrer"&gt;Helicone&lt;/a&gt; — Offers LLM monitoring with cost tracking and caching features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://arize.com" rel="noopener noreferrer"&gt;Arize&lt;/a&gt; — An observability and evaluation platform for AI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Vector databases
&lt;/h2&gt;

&lt;p&gt;Vector databases are data storage systems designed to handle high-dimensional vectors. They are essential for applications involving machine learning and AI. They enable efficient similarity searches and retrieval of data. You can use them for tasks such as recommendation systems and semantic search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qdrant.tech" rel="noopener noreferrer"&gt;Qdrant&lt;/a&gt; is a vector similarity search engine. It enables storing and searching through high-dimensional vectors using embeddings. The database offers filtering capabilities and real-time updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speech-to-text tools
&lt;/h2&gt;

&lt;p&gt;Speech-to-text (STT) technology helps applications to &lt;strong&gt;convert spoken words into written text&lt;/strong&gt;. There are several tools which can be helpful in that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://openai.com/research/whisper" rel="noopener noreferrer"&gt;Whisper&lt;/a&gt; by OpenAI offers transcription across many languages. You can run it locally or via API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.assemblyai.com/" rel="noopener noreferrer"&gt;AssemblyAI&lt;/a&gt; provides real-time transcription with advanced features like speaker diarization and content moderation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://deepgram.com" rel="noopener noreferrer"&gt;Deepgram&lt;/a&gt; specializes in real-time transcription optimized for specific industries and use cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.happyscribe.com" rel="noopener noreferrer"&gt;Happyscribe&lt;/a&gt; is another popular tool that offers transcription and subtitling services, providing an easy-to-use interface and API for seamless integration into various applications.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Text-to-speech tools
&lt;/h2&gt;

&lt;p&gt;Text-to-speech (TTS) allows applications to &lt;strong&gt;convert written text into natural-sounding speech&lt;/strong&gt;. This technology is essential for creating accessible applications and enhancing user experiences. There are tools for TTS as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://openai.com/research/text-to-speech" rel="noopener noreferrer"&gt;OpenAI TTS&lt;/a&gt; provides high-quality voice synthesis with customizable options for tone and style.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.elevenlabs.io/" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; offers realistic voice generation with emotional intonation, making it suitable for storytelling and interactive applications.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Image generation
&lt;/h2&gt;

&lt;p&gt;AI-powered image generation allows the &lt;strong&gt;production of high-quality visuals from textual descriptions or existing images&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://comfyui.com/" rel="noopener noreferrer"&gt;ComfyUI&lt;/a&gt; is an intuitive user interface for interacting with various AI models related to art and image synthesis. It enables users to configure and run models without extensive programming knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graph databases
&lt;/h2&gt;

&lt;p&gt;Graph databases can &lt;strong&gt;efficiently store data structured as graphs, namely those consisting of nodes (entities) and edges (relationships)&lt;/strong&gt;. This structure makes graph databases suitable for applications that need deep connections between data points, such as social networks, recommendation systems, and knowledge graphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://neo4j.com/" rel="noopener noreferrer"&gt;Neo4j&lt;/a&gt; is one of the most popular graph databases. It offers powerful querying capabilities through its Cypher query language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frameworks for agent creation
&lt;/h2&gt;

&lt;p&gt;There are several frameworks which can help you create AI agents. For example &lt;a href="https://www.crewai.com/" rel="noopener noreferrer"&gt;CrewAI&lt;/a&gt; provides a straightforward interface for building agents that can interact with various APIs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://sdk.vercel.ai/" rel="noopener noreferrer"&gt;Vercel AI SDK&lt;/a&gt;, likewise, is a powerful framework for building AI-powered user interfaces. It provides streaming responses and React/Svelte/Vue components, and has built-in support for popular AI models like OpenAI, Anthropic, and Hugging Face. The SDK makes it easy to implement features like chat interfaces with real-time streaming responses. It also offers type safety and handles rate limiting and error handling out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://langchain-ai.github.io/langgraph/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; focuses on integrating language models with graph databases, enabling developers to create agents that leverage complex relationships within data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/openai/swarm" rel="noopener noreferrer"&gt;Swarm&lt;/a&gt; (made by OpenAI, experimental at the time of writing) emphasizes collaborative agent behavior, allowing many agents to work together towards a common goal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/microsoft/autogen" rel="noopener noreferrer"&gt;AutoGen&lt;/a&gt; offers tools for automating the generation of agent behaviors and interactions, streamlining the development process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code interpreters for AI
&lt;/h2&gt;

&lt;p&gt;Code interpreters allow models to p &lt;strong&gt;erform complex tasks, such as web scraping or data processing&lt;/strong&gt;. Tools like &lt;a href="https://www.browserbase.com/" rel="noopener noreferrer"&gt;BrowserBase&lt;/a&gt; provide a user-friendly interface for automating browser interactions, making it easier to gather information from the web.&lt;/p&gt;

&lt;p&gt;Similarly, &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; offers powerful capabilities for browser automation, enabling developers to write scripts that can navigate web pages, fill out forms, and extract data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting techniques
&lt;/h2&gt;

&lt;p&gt;The development of AI applications requires specific approaches and methodologies. They differ from traditional software development. Here are some key techniques that have proven effective when building AI-powered systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function calling
&lt;/h2&gt;

&lt;p&gt;Function calling enables &lt;strong&gt;structured communication between the model and external tools or APIs&lt;/strong&gt;. You provide a schema describing available functions and their parameters. An LLM can decide when to use specific tools and generate the appropriate arguments on its own.&lt;/p&gt;

&lt;p&gt;This creates a standardized way for models to interact with external systems. For example, &lt;strong&gt;it can help searching databases, or controlling smart home devices.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One prompt for one problem
&lt;/h2&gt;

&lt;p&gt;A key principle in AI development is to &lt;strong&gt;break down complex tasks into smaller prompts&lt;/strong&gt;, rather than trying to achieve many objectives in a single go. &lt;strong&gt;Each prompt should be focused to address one specific problem or subtask.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This approach improves reliability and makes it easier to debug issues. For example, instead of asking an LLM to both analyze a document and generate a summary in one prompt, it’s better to split this into two steps. First analyzing the content, then creating the summary based on that analysis. This technique also reduces token usage which leads to lesser costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The AI_devs 3 course has provided valuable insights into building &lt;a href="https://www.thedroidsonroids.com/blog/ai-in-app-development-guide-for-developers" rel="noopener noreferrer"&gt;AI-powered applications&lt;/a&gt;. From basic concepts to advanced agent implementations.&lt;/p&gt;

&lt;p&gt;However, it’s important to understand that the field of AI development is evolving. &lt;strong&gt;New models, tools, and techniques emerge all the time&lt;/strong&gt;. For instance, the &lt;a href="https://www.deepseek.com/" rel="noopener noreferrer"&gt;Deepseek R1&lt;/a&gt; model wasn’t even available during that course. Now, it is a notable player in the field. This highlights why continuous learning is essential for AI developers.&lt;/p&gt;

&lt;p&gt;Taking one course, even an excellent one like AI_devs 3, is the beginning of the journey. &lt;strong&gt;You need to stay updated with the latest developments, constantly experiment with new tools, and refine your skills.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on my experience with AI_devs 3, &lt;strong&gt;I can recommend it, especially if you are interested in practical AI development.&lt;/strong&gt; I’m looking forward to participating in AI_devs 4 when it becomes available.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/deep-dive-ai-devs-3-course-insights" rel="noopener noreferrer"&gt;https://www.thedroidsonroids.com&lt;/a&gt; on February 20, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>programming</category>
      <category>rag</category>
    </item>
    <item>
      <title>10 Ways AI Can Speed Up your Mobile App Development</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Thu, 19 Sep 2024 08:57:50 +0000</pubDate>
      <link>https://dev.to/koral/10-ways-ai-can-speed-up-your-mobile-app-development-3278</link>
      <guid>https://dev.to/koral/10-ways-ai-can-speed-up-your-mobile-app-development-3278</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Today, AI (Artificial Intelligence) technologies can speed up the &lt;a href="https://www.thedroidsonroids.com/custom-mobile-app-development-services" rel="noopener noreferrer"&gt;mobile app development&lt;/a&gt; process. They can enhance mobile apps performance, and improve user experience. Developing AI powered apps is becoming more and more popular. From code generation and automated testing to user interface design and performance optimization, &lt;strong&gt;newer and newer app features depend on artificial intelligence and machine learning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No matter if you’re an Android, iOS, Flutter, or Kotlin Multiplatform developer, &lt;strong&gt;this guide provides tips and examples to help you use the power of AI algorithms in your daily work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My name is Karol, and I’ve been a mobile developer since 2011 (more about me in the bio below). I use AI tools every day in my work at &lt;a href="https://www.thedroidsonroids.com/" rel="noopener noreferrer"&gt;Droids On Roids&lt;/a&gt;, primarily through &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt;, including &lt;a href="https://docs.github.com/en/copilot/responsible-use-of-github-copilot-features/responsible-use-of-github-copilot-chat-in-your-ide#use-cases-for-github-copilot-chat" rel="noopener noreferrer"&gt;chat&lt;/a&gt;. Moreover, I use &lt;a href="https://hemingwayapp.com/" rel="noopener noreferrer"&gt;Hemingway&lt;/a&gt;, &lt;a href="https://openai.com/index/chatgpt/" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;, &lt;a href="https://www.anthropic.com/claude" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; Sonnet and &lt;a href="https://stability.ai/" rel="noopener noreferrer"&gt;Stable Diffusion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In November 2023, I completed the AI Devs 2 course. It covered generative AI, programming AI assistants, and other exciting topics. In November 2024 I’ll attend the &lt;a href="https://www.aidevs.pl/" rel="noopener noreferrer"&gt;AI Devs 3&lt;/a&gt; course. I’m excited to learn more about AI technology and how it can help me in my daily work. I want to share some popular AI tools that you’ll find valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic concepts
&lt;/h2&gt;

&lt;p&gt;There are several concepts you should understand before diving into the details of how to use AI in app development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Large Language Models
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Large_language_model" rel="noopener noreferrer"&gt;Large Language Models&lt;/a&gt; (LLMs) can generate &lt;strong&gt;human-like text based on the input they receive.&lt;/strong&gt; A Large Language Model (LLM) is an advanced type of artificial intelligence and machine learning. It can understand and generate human-like text based on the input it receives. These models are trained on extensive datasets, which contain a lot of text sources.&lt;/p&gt;

&lt;p&gt;Such models can perform &lt;strong&gt;a variety of language-related tasks&lt;/strong&gt;, like text completion, translation, summarization, and question-answering. LLMs leverage deep learning techniques, particularly transformer architectures, to capture the nuances and complexities of human language. This makes them powerful AI tools. GitHub Copilot and similar solutions use LLMs.&lt;/p&gt;

&lt;p&gt;LLMs can also be &lt;strong&gt;embedded inside AI powered mobile apps&lt;/strong&gt;. Current smartphones are powerful enough to handle a local LLM and the mobile apps using them do not even need to connect to the internet. If you want to read more about developing AI powered mobile apps, read my previous article: &lt;a href="https://www.thedroidsonroids.com/blog/ai-app-development-with-kotlin-multiplatform" rel="noopener noreferrer"&gt;How to develop an AI app with a local model in Kotlin Multiplatform&lt;/a&gt;. It describes how to create a simple AI powered mobile app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generative AI
&lt;/h2&gt;

&lt;p&gt;This type of AI technology can &lt;strong&gt;create new content, such as images, text, or music&lt;/strong&gt;. It looks for patterns by analyzing user data. Generative AI can be used to create data for training machine learning models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Generative_artificial_intelligence" rel="noopener noreferrer"&gt;Generative Artificial Intelligence&lt;/a&gt; also enables the creation of virtual environments and simulations. It is useful for mobile apps, such as &lt;strong&gt;gaming and virtual reality&lt;/strong&gt;. For example, Stable Diffusion can generate realistic images matching the text prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Natural Language Processing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Natural_language_processing" rel="noopener noreferrer"&gt;Natural Language Processing&lt;/a&gt; (NLP) is a field of artificial intelligence and machine learning. It focuses on &lt;strong&gt;the interaction between computers and humans through natural language&lt;/strong&gt;. This involves algorithms that enable machines to understand human language. Natural Language Processing encompasses a variety of tasks, such as language translation, sentiment analysis, speech recognition, and text summarization, for example.&lt;/p&gt;

&lt;p&gt;When it comes to AI powered mobile apps, Natural Language Processing can be used for the likes of &lt;strong&gt;voice-activated commands&lt;/strong&gt;. In this case, they allow app users to perform tasks hands-free, but Natural Language Processing can also power chatbots and &lt;strong&gt;virtual assistants&lt;/strong&gt;, helping AI powered mobile apps can provide instant customer support and personalized user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep learning
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Deep_learning" rel="noopener noreferrer"&gt;Deep Learning&lt;/a&gt; (DL) is** a subset of machine learning*&lt;em&gt;. It uses neural networks with many layers to model complex patterns by processing data. This creates many useful features and for mobile apps, such as **image and speech recognition, natural language processing, and autonomous driving&lt;/em&gt;*.&lt;/p&gt;

&lt;p&gt;DL has revolutionized the mobile app development industry. It leverages machine learning algorithms to create &lt;a href="https://www.thedroidsonroids.com/blog/best-ai-apps" rel="noopener noreferrer"&gt;AI apps&lt;/a&gt; that can perform complex tasks, including the aforementioned image recognition, as well as natural language processing, and predictive analytics. These models can enhance user experiences by providing personalized content and recommendations. For instance, AI apps can use DL to &lt;strong&gt;analyze user behavior and adapt the app’s functionality&lt;/strong&gt;. Additionally, Deep Learning facilitates the development of advanced features, such as &lt;strong&gt;real-time language translation **and **augmented reality&lt;/strong&gt;, making mobile applications more interactive and user-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How are developers using AI? Key stats
&lt;/h2&gt;

&lt;p&gt;Let me share some interesting results regarding developer usage**. **According to the &lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;2024 Stack Overflow Developer Survey&lt;/a&gt;, around 82% of developers are currently using AI tools for writing code. The next most popular uses include searching for answers, debugging, and documenting code. Interestingly, 46% of respondents want to start using AI for testing code. What’s more, nearly 40% are interested in leveraging AI for tasks like committing and reviewing code, predictive analytics, and even deployment and monitoring! Take a look at the popular uses of AI in the development according to &lt;a href="https://www.statista.com/statistics/1401409/popular-ai-uses-in-development-workflow-globally/" rel="noopener noreferrer"&gt;Statista&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ccl9cx1vlfp8keze3na.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ccl9cx1vlfp8keze3na.png" alt="Most popular uses of AI in the development workflow among developers worldwide as of 2024." width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This highlights how AI’s role in software development is growing fast. For software houses, &lt;strong&gt;it’s key to stay on top of these trends and use AI in smart, responsible ways to deliver solutions that are both efficient and high-quality.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you use AI in mobile app development?
&lt;/h2&gt;

&lt;p&gt;In this section, we will explore how you can integrate AI into mobile app development. I will cover various AI tools and virtual assistants that can enhance your app development process. Some ideas from my list overlap with the Stack Overflow survey results, but you’ll also find some unique insights. These come mainly from my own daily experiences working with AI tools. I hope you’ll find them helpful!&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-powered code generation
&lt;/h2&gt;

&lt;p&gt;When it comes to improving mobile app development, AI-powered code generation helps automate repetitive tasks and provide suggestions. Tools like &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; offer automated code completion. They help app developers write code faster and with fewer errors.&lt;/p&gt;

&lt;p&gt;Additionally, AI can &lt;strong&gt;generate boilerplate code&lt;/strong&gt;, which is common in app development. In the case of mobile apps, it can be used when setting up a new project. It is also good at &lt;strong&gt;generating repetitive code fragments&lt;/strong&gt; like subsequent form fields. Such tools can save developers a significant amount of time and effort. &lt;a href="https://codeium.com/" rel="noopener noreferrer"&gt;Codeium&lt;/a&gt; and &lt;a href="https://www.tabnine.com/" rel="noopener noreferrer"&gt;Tabnine&lt;/a&gt;, for instance, &lt;strong&gt;provide smart code completions&lt;/strong&gt;. They base their predictions on the context of the written code. Most of these tools are available as plugins for popular IDEs used by mobile app developers, like Android Studio or Xcode.&lt;/p&gt;

&lt;p&gt;To get the most out of GitHub Copilot, &lt;strong&gt;use clear and descriptive comments&lt;/strong&gt;. Look at the official &lt;a href="https://github.blog/developer-skills/github/how-to-use-github-copilot-in-your-ide-tips-tricks-and-best-practices/" rel="noopener noreferrer"&gt;article about tips, tricks, and best practices&lt;/a&gt;. It’ll guide the AI in generating relevant code snippets. Don’t forget to review the generated code and ensure it meets your project’s requirements and standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-assisted testing and debugging
&lt;/h2&gt;

&lt;p&gt;AI-assisted testing and debugging tools automate various testing tasks and related work. There are test case generation solutions, such as &lt;a href="http://testim" rel="noopener noreferrer"&gt;Testim&lt;/a&gt; and &lt;a href="https://applitools.com/" rel="noopener noreferrer"&gt;Applitools&lt;/a&gt;, that use AI to create test cases, &lt;strong&gt;ensuring better coverage and reducing manual effort&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI tools mentioned in the previous section can help in writing unit tests. They can also generate other types of tests written as code, such as integration and end-to-end tests.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://snyk.io/platform/deepcode-ai/" rel="noopener noreferrer"&gt;DeepCode&lt;/a&gt; analyze code to &lt;strong&gt;identify potential bugs before they occur&lt;/strong&gt;. They help developers fix issues early in the app development cycle, which can &lt;strong&gt;significantly reduce operational costs and the overall time spent on debugging&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-driven user interface design
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;a href="https://uizard.io/" rel="noopener noreferrer"&gt;Uizard&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/shows/ai-show/sketch2code" rel="noopener noreferrer"&gt;Sketch2Code&lt;/a&gt; generate UI elements based on design specifications. They can &lt;strong&gt;convert design mockups into functional code&lt;/strong&gt;, reducing manual effort. AI can also &lt;strong&gt;optimize layouts for different screen sizes&lt;/strong&gt;. Tools like &lt;a href="https://www.framer.com/features/ai/" rel="noopener noreferrer"&gt;Framer&lt;/a&gt;, for instance, use AI this way. It adjusts UI components to fit different screen dimensions.&lt;/p&gt;

&lt;p&gt;Additionally, AI can &lt;strong&gt;suggest design improvements&lt;/strong&gt; based on user experience data. It helps developers &lt;strong&gt;create more intuitive and user-friendly interfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://stability.ai/" rel="noopener noreferrer"&gt;Stable Diffusion&lt;/a&gt; and &lt;a href="https://www.midjourney.com/home" rel="noopener noreferrer"&gt;MidJourney&lt;/a&gt; can &lt;strong&gt;generate realistic images&lt;/strong&gt;. They do that basing on textual descriptions. Those tools can reduce the time spent on manual design tasks. This enables development team members to focus on the more creative aspects of mobile app development.&lt;/p&gt;

&lt;p&gt;Uizard uses AI in its design-to-code feature. It can convert your design mockups into functional UI code. Additionally, its suggestions can optimize your layouts for different screen sizes and orientations.&lt;/p&gt;

&lt;p&gt;To get the most out of Stable Diffusion, focus on crafting &lt;strong&gt;detailed, specific text prompts&lt;/strong&gt;. They should precisely describe the desired output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Natural Language Processing for app localization
&lt;/h2&gt;

&lt;p&gt;Translation tools like &lt;a href="https://lokalise.com/ai" rel="noopener noreferrer"&gt;Lokalise&lt;/a&gt; and &lt;a href="https://www.transifex.com/ai/" rel="noopener noreferrer"&gt;Transifex&lt;/a&gt; have advanced Natural Language Processing capabilities nowadays. They can &lt;strong&gt;translate a mobile app’s content into many languages&lt;/strong&gt;. Such translations are not only accurate but also culturally relevant. They can be used not only inside mobile applications but also for text in the app stores.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://phrase.com/platform/ai/" rel="noopener noreferrer"&gt;Phrase&lt;/a&gt; and &lt;a href="https://www.smartling.com/" rel="noopener noreferrer"&gt;Smartling&lt;/a&gt; also use Natural Language Processing. They consider the context in which the text appears. When using Smartling, integrate it early in the app development process. That way, it can capture content as it’s created&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-powered performance optimization
&lt;/h2&gt;

&lt;p&gt;AI technology helps in resource allocation and management. Tools such as &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; can &lt;strong&gt;dynamically allocate resources&lt;/strong&gt; based on current demand. This ensures optimal performance and cost efficiency.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://www.akamas.io/" rel="noopener noreferrer"&gt;Akamas&lt;/a&gt;, similarly, can determine the caching mechanisms to reduce load times. Then, they balance the loads across servers, helping to maintain a high responsiveness and prevent bottlenecks.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-enhanced security implementation
&lt;/h2&gt;

&lt;p&gt;There are &lt;strong&gt;automated vulnerability detection and patching tools&lt;/strong&gt;, such as &lt;a href="https://snyk.io/" rel="noopener noreferrer"&gt;Snyk&lt;/a&gt; and &lt;a href="http://mend.io" rel="noopener noreferrer"&gt;Mend.io&lt;/a&gt;. They can constantly scan codebases for &lt;a href="https://www.thedroidsonroids.com/blog/what-is-application-security-all-you-need-to-know-guide" rel="noopener noreferrer"&gt;app security&lt;/a&gt; vulnerabilities and apply patches automatically. Consequently, they reduce the risk of exploitation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; by Okta provides Intelligent user authentication systems. It uses AI technology to analyze user behavior and implement adaptive authentication mechanisms. It can help ensure secure access while minimizing friction for legitimate users.&lt;/p&gt;

&lt;p&gt;Threat analysis and prevention tools, such as Darktrace and Vectra, also use AI algorithms. They monitor network traffic and detect anomalies. They can help with proactive threat mitigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-driven user behavior analysis and personalization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Predictive user modeling tools&lt;/strong&gt;, such as &lt;a href="https://mixpanel.com/spark-ai" rel="noopener noreferrer"&gt;Mixpanel&lt;/a&gt; and &lt;a href="https://amplitude.com/ai" rel="noopener noreferrer"&gt;Amplitude&lt;/a&gt;, analyze user behavior data. They can &lt;strong&gt;forecast future users’ actions and preferences&lt;/strong&gt;. Automated A/B testing tools, like Optimizely and Google Optimize, use AI. They can run experiments, predict user actions and identify the most effective variations.&lt;/p&gt;

&lt;p&gt;Content recommendation systems, like those provided by &lt;a href="https://www.recombee.com/" rel="noopener noreferrer"&gt;Recombee&lt;/a&gt; and &lt;a href="https://www.algolia.com/products/ai-search/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;, also utilize AI algorithms. They &lt;strong&gt;analyze users’ behavior, interactions and preferences to suggest relevant content&lt;/strong&gt;. For example, let’s assume that a user watches several action movies. The AI will recommend other action movies or related genres in real-time. Or, let’s say that a user listens to music on their mobile device during the morning commute. The AI will suggest similar music during that time frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-assisted API integration and management
&lt;/h2&gt;

&lt;p&gt;Tools such as &lt;a href="https://newrelic.com/platform/new-relic-ai" rel="noopener noreferrer"&gt;New Relic&lt;/a&gt; and &lt;a href="https://www.dynatrace.com/platform/artificial-intelligence/" rel="noopener noreferrer"&gt;Dynatrace&lt;/a&gt; also use AI algorithms. They do this for &lt;strong&gt;API performance monitoring and optimization&lt;/strong&gt;. They check API performance, detect anomalies, and provide actionable insights for optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-powered asset management and optimization
&lt;/h2&gt;

&lt;p&gt;Media compression tools, such as &lt;a href="https://cloudinary.com/products/cloudinary_ai" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;, use AI as well. They reduce file sizes without compromising quality. &lt;a href="https://www.akamai.com/" rel="noopener noreferrer"&gt;Akamai&lt;/a&gt; and &lt;a href="https://www.fastly.com/" rel="noopener noreferrer"&gt;Fastly&lt;/a&gt; utilize AI algorithms to manage versions of assets and cache them effectively. &lt;strong&gt;This can reduce server load and improve delivery speed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt; use AI to analyze user behavior. It can then load the most relevant resources faster, resulting in optimized performance and user engagement.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-enhanced collaboration and project management
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://asana.com/product/ai" rel="noopener noreferrer"&gt;Asana&lt;/a&gt; and &lt;a href="https://www.atlassian.com/blog/work-management/introducing-ai-powered-trello" rel="noopener noreferrer"&gt;Trello&lt;/a&gt; use AI to assign tasks based on team members’ skills and availability. &lt;a href="https://monday.com/w/ai" rel="noopener noreferrer"&gt;Monday&lt;/a&gt; and &lt;a href="https://www.atlassian.com/software/jira" rel="noopener noreferrer"&gt;Jira&lt;/a&gt;, likewise, use AI to &lt;strong&gt;track project milestones&lt;/strong&gt;. They generate real-time status reports, and provide insights into project health.&lt;/p&gt;

&lt;p&gt;Deadline and resource management tools, such as &lt;a href="https://clickup.com/ai" rel="noopener noreferrer"&gt;ClickUp&lt;/a&gt; and &lt;a href="https://www.wrike.com/features/work-intelligence/" rel="noopener noreferrer"&gt;Wrike&lt;/a&gt;, are other noteworthy examples. They employ AI to forecast project timelines, help identify potential bottlenecks, and suggest resource adjustments to meet deadlines.&lt;/p&gt;

&lt;p&gt;Let’s sum up what we’ve discussed in this part:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulp6pr3u3qfx08dsqdre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulp6pr3u3qfx08dsqdre.png" alt="How AI can assist in mobile app development" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;AI is revolutionizing mobile app development by enhancing various aspects of the process. It has become more and more popular in the mobile app industry. For example, AI-powered code generation tools like GitHub Copilot speed up coding, while graphic tools like Stable Diffusion generate the assets. AI can do more and more time consuming and repetitive tasks. This all leads to &lt;strong&gt;faster app development cycles and lesser costs&lt;/strong&gt;. At &lt;a href="https://www.thedroidsonroids.com/" rel="noopener noreferrer"&gt;Droids On Roids&lt;/a&gt;, we use AI tools such as GitHub Copilot and ChatGPT to accelerate software creation and reduce costs for our clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips &amp;amp; tricks
&lt;/h2&gt;

&lt;p&gt;When using AI tools, it’s important to understand the problem you’re trying to solve. You should specify the desired outcomes. **The more precise you are, the better results you’ll get. **Carefully check the capabilities and limitations of each AI tool. Ensure it aligns with your requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t forget to check the terms and conditions.&lt;/strong&gt; Some tools may use data you enter to train the models. If that data is confidential, check if you can disable such learning. Otherwise, your data may appear to other users of the given tool.&lt;/p&gt;

&lt;p&gt;Sometimes you may need to buy a more expensive subscription to increase privacy. &lt;strong&gt;Always maintain human oversight and control&lt;/strong&gt; to ensure responsible and ethical implementation. Additionally, be prepared to experiment, and iterate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/ai-in-app-development-guide-for-developers" rel="noopener noreferrer"&gt;https://www.thedroidsonroids.com&lt;/a&gt; on September 19, 2024.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>How to develop an AI app with a local model in Kotlin Multiplatform</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Fri, 30 Aug 2024 10:29:19 +0000</pubDate>
      <link>https://dev.to/koral/how-to-develop-an-ai-app-with-a-local-model-in-kotlin-multiplatform-3jk7</link>
      <guid>https://dev.to/koral/how-to-develop-an-ai-app-with-a-local-model-in-kotlin-multiplatform-3jk7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Kotlin Multiplatform&lt;/strong&gt; (KMP) is a technology that enables you to write code once and run it on many platforms. It’s a great way to share code between Android and iOS apps (read also: &lt;a href="https://www.thedroidsonroids.com/blog/flutter-vs-kotlin-multiplatform" rel="noopener noreferrer"&gt;Flutter vs Kotlin Multiplatform&lt;/a&gt;). With the addition of AI, which stands for artificial intelligence, apps can perform tasks that usually require human intelligence by themselves. In this article, I will show you how to create an AI app using &lt;a href="https://www.thedroidsonroids.com/blog/what-is-kotlin-multiplatform" rel="noopener noreferrer"&gt;Kotlin Multiplatform&lt;/a&gt; and a &lt;strong&gt;local AI model&lt;/strong&gt;, with a technical focus specifically for developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kotlin Multiplatform supports mobile platforms like Android and iOS. It supports desktop and web apps as well but, in this article, &lt;strong&gt;I will only focus on Android and iOS&lt;/strong&gt;. The app will use &lt;strong&gt;the &lt;a href="https://www.jetbrains.com/lp/compose-multiplatform/" rel="noopener noreferrer"&gt;Compose Multiplatform&lt;/a&gt; UI framework for the user interface&lt;/strong&gt;. It’s like the Jetpack Compose widely used in &lt;a href="https://www.thedroidsonroids.com/services/android-app-development-company" rel="noopener noreferrer"&gt;native Android app development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI in the app will classify whether entered texts are positive or negative.&lt;/strong&gt; It will use a TensorFlow Lite model and MediaPipe libraries for this purpose. Despite the fact that the model itself is platform-independent, the MediaPipe libraries are not. Therefore, we need to write a little bit of platform-specific code.&lt;/p&gt;

&lt;p&gt;Note that Compose Multiplatform for &lt;a href="https://www.thedroidsonroids.com/services/ios-app-development-company" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; is still (as of August 2024) in the &lt;a href="https://www.jetbrains.com/help/kotlin-multiplatform-dev/supported-platforms.html#compose-multiplatform-ui-framework-stability-levels" rel="noopener noreferrer"&gt;beta stage&lt;/a&gt;. Kotlin Multiplatform itself is stable but is in very active development. AI is a rapidly evolving technology, however, so the code in this article may become outdated soon. Keep that in mind if you are reading this article a long time after the publication date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The app I’ll show you is very simple. &lt;strong&gt;It is rather a proof of concept&lt;/strong&gt; than a production-ready product. The goal is to show you how to integrate an AI in a Kotlin Multiplatform app. In the real app, you would probably want to use a more sophisticated AI model. You should also &lt;strong&gt;take care about architecture, error handling, and performance&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI
&lt;/h2&gt;

&lt;p&gt;The app will have a text field where the user can enter some text. The app will then classify that text and show the results. The user will see a message indicating whether the entered text is positive or negative. The UI will look like this on Android:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkvb00880srhcgqe4h3k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkvb00880srhcgqe4h3k.png" alt="Screenshot of the Android app showing the question and answer" width="461" height="893"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And like this on iOS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwcxedo6s9bcvfa3lwr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwcxedo6s9bcvfa3lwr8.png" alt="Screenshot of the iOS app showing the question and answer" width="461" height="998"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the app in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1004440333" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;All the UI code will be in the &lt;strong&gt;shared module&lt;/strong&gt;, common for all the platforms. The UI will be defined in a composable function. The entire code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="nd"&gt;@Composable&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;rememberSaveable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
          &lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;maxLines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Text to classify"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
            &lt;span class="n"&gt;onValueChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotBlank&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Category: ${classify(text)}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A state variable that holds the text entered by the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A text field composable that enables the user to enter text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Synchronizing the text inside a field with the state variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updating the state variable when the user enters text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Showing the classification result only when the user has entered some text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classifying the entered text and showing the result.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The TextField is &lt;strong&gt;stateless&lt;/strong&gt;, it does not hold the text entered by the user. It only provides the ability to set its value and a &lt;strong&gt;callback&lt;/strong&gt; when that value gets changed. That's why you need a separate text variable to hold the state. It also serves as the source of data for the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note the rememberSaveable and the mutableStateOf&lt;/strong&gt; functions. You may ask why they are needed? Isn't it enough to just use a var variable? No, it's not. You need both of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need to wrap the actual text (string) in a mutable state to make it observable by the Compose framework.&lt;/strong&gt; It needs to know when the text changes to trigger the classification process. The plain variable is not &lt;strong&gt;observable&lt;/strong&gt;. When the framework detects the change, it calls the affected composable functions again. By “affected” I mean those in which the change occurred, so the text variable will reset to the empty string.&lt;/p&gt;

&lt;p&gt;This is why you need the rememberSaveable function. &lt;strong&gt;Its lambda gets called only once when the composable is first created.&lt;/strong&gt; It remembers the value of the text during all the next updates (called recompositions). What is more, this function is saveable so it survives the process' deaths, such as when the app goes to the background and gets killed by the system to free up memory, for example.&lt;/p&gt;

&lt;p&gt;The MaterialTheme and modifiers (padding and fill max width) are here t &lt;strong&gt;o make the UI look nice&lt;/strong&gt;. The reason for the ten lines limit for the text field is similar. It prevents the text field from occupying too much of the screen. The label of the text field and the classification result prefix are hardcoded for simplicity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the real app, you would need a more sophisticated UI.&lt;/strong&gt; You should use the localized string resources for labels (Compose Multiplatform supports that), so they can be translated to many languages. You would also need to handle the case when the user enters too much text. This could be done, for example, by displaying some character counters.&lt;/p&gt;

&lt;p&gt;The Text composable shows the &lt;strong&gt;classification result&lt;/strong&gt;. It gets recalculated in every typed character. It's not a problem for such a simple app. But in a real app with more complex models, you would want to add some debouncing mechanism to ensure the classification is not triggered too often.&lt;/p&gt;

&lt;p&gt;For example, you may trigger it when the user stops typing for a half second. Such &lt;strong&gt;debouncing&lt;/strong&gt; is a must-have if the model is not local but provided by a remote server. In such cases, you often pay for each token (which is roughly each word in English).&lt;/p&gt;

&lt;p&gt;In the app from this article, the classification happens &lt;strong&gt;synchronously&lt;/strong&gt; on the main (UI) thread. It may look seamless in this simple app but, in a real application, you should perform such heavy tasks &lt;strong&gt;asynchronously&lt;/strong&gt; on the &lt;strong&gt;background&lt;/strong&gt; threads. Otherwise, without it, the app may freeze for a moment. You may use the &lt;strong&gt;Kotlin Coroutines&lt;/strong&gt; for that purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform-specific code
&lt;/h2&gt;

&lt;p&gt;The signature of the classify function from the previous snippet is as follows:&lt;/p&gt;

&lt;p&gt;internal expect fun classify(text: String): String&lt;/p&gt;

&lt;p&gt;Note the expect keyword. It means that the implementation of the function is platform-specific. You may have many implementations of the function in different platform-specific modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android
&lt;/h3&gt;

&lt;p&gt;Take a look at the Android implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textClassifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classificationResult&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classifications&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;maxBy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the actual keyword. It indicates the actual implementation of the expected function. This classification process uses the &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifier" rel="noopener noreferrer"&gt;TextClassifier&lt;/a&gt; class from the &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/guide" rel="noopener noreferrer"&gt;MediaPipe&lt;/a&gt; library. The steps are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Passing the text to the model and getting the response. This is the most time-consuming step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/ClassificationResult" rel="noopener noreferrer"&gt;classification result&lt;/a&gt; from the response. Apart from the result, a response contains also the &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/ClassificationResult#timestampMs()" rel="noopener noreferrer"&gt;timestamp&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the list of &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/Classifications" rel="noopener noreferrer"&gt;classifications&lt;/a&gt; from the result. The size of the list depends on the model. In this case, it’s always one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the first classification from the list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the list of &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/Category" rel="noopener noreferrer"&gt;categories&lt;/a&gt; from the classification. The categories depend on the model. In this case, there are only two: “positive” and “negative”.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finding the category with the highest probability (&lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/Category#score()" rel="noopener noreferrer"&gt;score&lt;/a&gt;). Each classification contains a list of categories with their scores. The latter sum up to one (100%).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting the &lt;a href="https://ai.google.dev/edge/api/mediapipe/java/com/google/mediapipe/tasks/components/containers/Category#categoryName()" rel="noopener noreferrer"&gt;name&lt;/a&gt; of the category. The names are hardcoded in the model.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This simple app extracts only the name of the most probable &lt;strong&gt;category&lt;/strong&gt;. In more complex models, there might be more than one classification result for a given input, as well as more than two categories for each classification. You may also use the scores to show the user how sure the result is.&lt;/p&gt;

&lt;p&gt;The textClassifier is a singleton instance of the TextClassifier class. It's initialized in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;lateinit&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;textClassifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TextClassifier&lt;/span&gt;

    &lt;span class="kd"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;baseOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;BaseOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setModelAssetPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mobilebert.tflite"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TextClassifierOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBaseOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="n"&gt;textClassifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TextClassifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFromOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initClassifier function is invoked from the initializer provided by the &lt;a href="https://www.google.com/search?q=androidx+startup&amp;amp;oq=androidx+startup&amp;amp;gs_lcrp=EgZjaHJvbWUyCQgAEEUYORiABDIHCAEQABiABDIICAIQABgWGB4yCggDEAAYChgWGB4yCAgEEAAYFhgeMggIBRAAGBYYHjIICAYQABgWGB4yCAgHEAAYFhgeMggICBAAGBYYHjINCAkQABiGAxiABBiKBdIBCDMzNzJqMGo3qAIAsAIA&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8" rel="noopener noreferrer"&gt;AndroidX App Startup&lt;/a&gt; library. This ensures that the model is always ready before the user can enter any text. Moreover, the initializer gets triggered only once when the app is started.&lt;/p&gt;

&lt;p&gt;Note that the model is stored in the mobilebert.tflite file. It is inside the Android &lt;strong&gt;assets&lt;/strong&gt; directory. The size of the model is about 25 MB. Loading such a big model may take a while. In the real app, you should perform the loading in the &lt;strong&gt;background&lt;/strong&gt;. Keep in mind that the model files may be bigger. They may be even so big that they do not fit in the largest size of the application on the Play Store. In such cases, you may want to download the model from the server from inside the app.&lt;/p&gt;

&lt;p&gt;The TextClassifier has to be closed when no longer needed to prevent memory leaks. You can do this by calling the close method. In this simple app it's not necessary because there is only one, singleton instance of the TextClassifier. In the real app, however, you should close the classifiers when they are no longer needed, especially when you have many classifiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS
&lt;/h3&gt;

&lt;p&gt;Most of the iOS-specific code is written in Swift. On the Kotlin side, there is only a small bridge using the mentioned actual keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;lateinit&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;@Suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unused"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Called from Swift&lt;/span&gt;
    &lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nativeClassifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nativeClassifier&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation in Swift is analogous to the Android one. Take a look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mobilebert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"tflite"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;TextClassifierOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baseOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelAssetPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modelPath&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;textClassifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;TextClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Classifier_iosKt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doInitClassifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;in&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;textClassifier&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classificationResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classifications&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;
       &lt;span class="p"&gt;}?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"unknown"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only significant difference is that both initialization and classification can throw errors. So, the function calls are wrapped in the try? operator and &lt;strong&gt;guarded&lt;/strong&gt; by the safe calls (?.). In case of an error, the resulting category passed to the Kotlin falls back to "unknown".&lt;/p&gt;

&lt;p&gt;Note that unhappy scenarios can also happen on Android. For example, when the model file turns out to be missing or corrupted. In such cases, the affected function call throws an unchecked exception and, as a result the app crashes. In the real app, &lt;strong&gt;you should handle such exceptions&lt;/strong&gt;, especially when you use models downloaded from the internet.&lt;/p&gt;

&lt;p&gt;Note the lateinit classifier variable. It is not possible to call the Swift function directly from the Kotlin code. But the opposite is possible. The Kotlin classes and top-level functions are visible in Swift. So, during initialization, the Swift code creates a callback. It is then stored on the Kotlin side and called when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model
&lt;/h2&gt;

&lt;p&gt;The model used in the app is a &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/text/text_classifier" rel="noopener noreferrer"&gt;MobileBERT model&lt;/a&gt;. It’s trained on the &lt;a href="https://huggingface.co/datasets/stanfordnlp/sst2" rel="noopener noreferrer"&gt;SST-2&lt;/a&gt; (Stanford Sentiment Treebank) dataset, which contains movie reviews. The model is exported to the &lt;a href="https://www.tensorflow.org/lite/models/modify/model_maker/text_classification#export_as_a_tensorflow_lite_model" rel="noopener noreferrer"&gt;TensorFlow Lite&lt;/a&gt; format, which is &lt;strong&gt;optimized for mobile devices&lt;/strong&gt;, so it can be used locally on the user’s phone, without the need to send any data to the internet! It may be important in terms of privacy and legal regulations. The model file is the same for both Android and iOS.&lt;/p&gt;

&lt;p&gt;Although it is possible to create a model from scratch, it’s a very time-consuming process. Take a look at the below visualization of just part of the MobileBERT model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwijn7o1o7k4amvv2qm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwijn7o1o7k4amvv2qm8.png" alt="Visualization of part of MobileBERT model" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the size of the scrollbars. The model is huge. You can explore it using &lt;a href="https://netron.app/" rel="noopener noreferrer"&gt;Netron&lt;/a&gt;. In the real app, you would use some ready, pre-trained model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;In this article, you learned how to create an AI-powered application using &lt;strong&gt;Kotlin Multiplatform&lt;/strong&gt;. I demonstrated to you how to use the &lt;strong&gt;Compose Multiplatform&lt;/strong&gt; UI framework. You can use it to build a user interface that works on both &lt;strong&gt;Android&lt;/strong&gt; and &lt;strong&gt;iOS&lt;/strong&gt;. I also showed you how to integrate a &lt;strong&gt;local&lt;/strong&gt; &lt;strong&gt;TensorFlow Lite&lt;/strong&gt; &lt;strong&gt;model&lt;/strong&gt; in the app and how to use the &lt;strong&gt;MediaPipe&lt;/strong&gt; libraries to classify text. With this knowledge, you can develop apps which don’t require an internet connection to perform data processing.&lt;/p&gt;

&lt;p&gt;You should now have a rough understanding of how to build a simple AI app using &lt;a href="https://www.thedroidsonroids.com/blog/what-is-kotlin-multiplatform" rel="noopener noreferrer"&gt;Kotlin Multiplatform&lt;/a&gt;. Keep in mind that AI and Kotlin Multiplatform are rapidly evolving fields. So, always stay updated with the latest library versions and best practices.&lt;/p&gt;

&lt;p&gt;If you’re looking for a tech partner to build your AI app, &lt;a href="https://www.thedroidsonroids.com/estimate-project" rel="noopener noreferrer"&gt;reach out to us&lt;/a&gt; and schedule a free consultation.&lt;/p&gt;

&lt;p&gt;The full source code is available on our GitHub &lt;a href="https://github.com/DroidsOnRoids/article-ai-kmp" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/ai-app-development-with-kotlin-multiplatform" rel="noopener noreferrer"&gt;https://www.thedroidsonroids.com&lt;/a&gt; on August 30, 2024.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>ios</category>
      <category>ai</category>
    </item>
    <item>
      <title>Edge Cases to Keep in Mind. Part 1 — Text</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Thu, 08 Aug 2024 17:32:39 +0000</pubDate>
      <link>https://dev.to/koral/edge-cases-to-keep-in-mind-part-1-text-4f36</link>
      <guid>https://dev.to/koral/edge-cases-to-keep-in-mind-part-1-text-4f36</guid>
      <description>&lt;p&gt;&lt;strong&gt;No matter if you are a software developer, a copywriter or you’re just writing an e-mail, text has many traps you need to be aware of. Some may cause numerous issues, from bugs in your app through visual artefacts to even victims! Let’s take a look at how we can avoid them.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Text (aka strings) exists in virtually all software projects, from one-liners like hello-worlds to enterprise systems containing billions of lines of code, regardless of the programming language, platform and so on. Texts are just sequences of characters, so this shouldn’t be rocket science, right? Let’s take a look what traps you can encounter!&lt;/p&gt;

&lt;h3&gt;
  
  
  Letter case
&lt;/h3&gt;

&lt;p&gt;Some of the world’s alphabets (including English) are bicameral, which means they contain both upper and lower case letters.&lt;br&gt;
 For example: a is a lower case character and A is upper case. Conversion from one letter case to another is quite a common operation.&lt;/p&gt;

&lt;p&gt;Casing might seem trivial — one character is just converted (mapped) to another one. It may even be a character unto itself if it is not a letter, such as 1 or + and so on. Additionally, this mapping can always be simply reversed, e.g. A-&amp;gt;a and a-&amp;gt;A. So, everything seems fine at first glance. Well, nothing could be further from the truth!&lt;/p&gt;

&lt;h3&gt;
  
  
  Casing errors may kill
&lt;/h3&gt;

&lt;p&gt;This is not a joke and we are not talking about enraged grammar Nazis. As you can read in &lt;a href="http://gizmodo.com/382026/a-cellphones-missing-dot-kills-two-people-puts-three-more-in-jail" rel="noopener noreferrer"&gt;this article&lt;/a&gt;, a casing glitch caused 2 victims and put 3 more people in jail.&lt;/p&gt;

&lt;p&gt;How did that happen? Well, in Turkish (and Azeri) we have 2 distinct i letters: dotted (closed) and dotless (open). In English and other Latin alphabets, lowercase letter is always dotted while uppercase - dotless. Everything is illustrated in Table 1. and &lt;a href="https://goo.gl/UN3dVX" rel="noopener noreferrer"&gt;online demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Table 1. Dotted and dotless i letters. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Lowercase&lt;/th&gt;
&lt;th&gt;Uppercase&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;i dotted&lt;/td&gt;
&lt;td&gt;I dotless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish&lt;/td&gt;
&lt;td&gt;i dotted&lt;/td&gt;
&lt;td&gt;İ dotted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish&lt;/td&gt;
&lt;td&gt;ı dotless&lt;/td&gt;
&lt;td&gt;I dotless&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the casing change result depends on context, which further depends on the current language. It is important to use appropriate language when composing texts intended for humans. If you don’t care about this, your words may end up having a different meaning than intended.&lt;/p&gt;

&lt;p&gt;On the other hand, machine readable texts like HTTP headers or JSON keys should be processed in language-neutral way. Otherwise, you may get non-ASCII characters in the output which may break application logic. That exact situation &lt;a href="https://github.com/google/gson/pull/652" rel="noopener noreferrer"&gt;happened in GSON&lt;/a&gt;, a library used by thousands (or maybe millions) of projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  The secrets of diacritics
&lt;/h3&gt;

&lt;p&gt;Characters with &lt;a href="https://en.wikipedia.org/wiki/Diacritic" rel="noopener noreferrer"&gt;diacritics&lt;/a&gt; can be precomposed like ó, or created by combining marks like ó. When reading this page, they both look like the same character. Yet, if look at the hexdump of the second one or even try to obtain its length programmatically, like in this &lt;a href="https://goo.gl/4y6byK" rel="noopener noreferrer"&gt;demo&lt;/a&gt;, you will see that it consist of 2 individual characters: &lt;a href="http://www.fileformat.info/info/unicode/char/006f/index.htm" rel="noopener noreferrer"&gt;Latin small letter o&lt;/a&gt; and a &lt;a href="http://www.fileformat.info/info/unicode/char/0301/index.htm" rel="noopener noreferrer"&gt;combining acute accent&lt;/a&gt;. Similarly, each &lt;a href="https://en.wikipedia.org/wiki/Hangul" rel="noopener noreferrer"&gt;Hangul&lt;/a&gt; (Korean alphabet) syllable block can be precomposed or written as a combination of distinct &lt;a href="https://en.wikipedia.org/wiki/Hangul#Letters" rel="noopener noreferrer"&gt;jamos&lt;/a&gt; individual letters/characters.&lt;/p&gt;

&lt;p&gt;Why are combining marks so important? Well, there are two ways of writing most of the characters with diacritics (for example from Polish, Hungarian or Czech alphabets). This makes operations like sorting, searching or text length measuring non-trivial. Usually, to achieve the best user experience, texts need to be normalized (converted to one of the normal forms). Otherwise, users may be confused when they see, for instance, multiple “different” logins or filenames that look the same. A great example of this is how Slack handles channel names. They are normalized before channel creation, so situations wherein the same name written in different ways cannot coexist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some characters are more equal than others
&lt;/h3&gt;

&lt;p&gt;There are 2 levels of character equivalence. &lt;strong&gt;Canonical equivalence&lt;/strong&gt; occurs when characters are assumed to have the same both meaning and appearance, e.g. the aforementioned ó and ó differ only by (technical) way of writing. On the other hand, &lt;strong&gt;compatibility&lt;/strong&gt; means that characters may appear distinct but may have the same meaning. For example the &lt;a href="https://en.wikipedia.org/wiki/Typographic_ligature" rel="noopener noreferrer"&gt;ligature&lt;/a&gt; ﬃ is compatible with three distinct letters ffi but they are not canonically equal. More info about Unicode normalization can be found in &lt;a href="http://unicode.org/reports/tr15/" rel="noopener noreferrer"&gt;the standard documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While both the composed and decomposed forms for each 2 levels are standardized — so we have 4 normal forms in total — normalization is not always reversible. For example an &lt;a href="http://www.fileformat.info/info/unicode/char/212b/index.htm" rel="noopener noreferrer"&gt;angstrom sign&lt;/a&gt; Å is decomposed to the &lt;a href="http://www.fileformat.info/info/unicode/char/0041/index.htm" rel="noopener noreferrer"&gt;Latin capital letter A&lt;/a&gt; A plus &lt;a href="http://www.fileformat.info/info/unicode/char/030a/index.htm" rel="noopener noreferrer"&gt;combining ring above&lt;/a&gt; ̊, which are composed back to a &lt;a href="http://www.fileformat.info/info/unicode/char/00c5/index.htm" rel="noopener noreferrer"&gt;Latin capital letter A with ring above&lt;/a&gt; Å, not to the angstrom sign from which it originated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combo normalization bugs cause adventures
&lt;/h3&gt;

&lt;p&gt;It is also important that all the applications sharing a given text use the same normalization method. If they don’t, it may cause subtle errors and/or even silent data losses. Such bugs may be difficult to discover because each application works faultlessly, at least when running individually. Applications often do not “crash” in such cases but just send or receive data different to what it should, causing unintended consequences. One such examples is &lt;a href="https://sourceforge.net/p/netatalk/bugs/348/" rel="noopener noreferrer"&gt;this bug in nettalk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aforementioned typographic ligatures are used to improve the visual appearance of certain characters which don’t look well separately adjacent to each other. Most users don’t need to worry about ligatures, since they are generated automatically from individual letters by software e.g. &lt;a href="http://tug.org/" rel="noopener noreferrer"&gt;TeX&lt;/a&gt; produces ligatures by default. However, developers of such tools have to take into account that, in some cases, ligatures may be inappropriate and introduce errors.&lt;/p&gt;

&lt;p&gt;Take a look at this: ﬁ. Is the second letter dotted or dotless? Turkish-speaking readers may be confused. Ligatures containing i should not be used in some contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is my uppercase?
&lt;/h3&gt;

&lt;p&gt;A few scripts (so-called bicameral) like Latin and Greek contain letters of two cases. Virtually all of the letters have lower and upper case. Virtually… but not absolutely all!&lt;br&gt;
 While the lowercase set is always present, it is not true for uppercase. So, if there are characters which have only lowercase, what happens if you try to convert them to uppercase? Would it be an error that causes the operation to fail? Would the character stay the same? The answer is nothing of that kind!&lt;/p&gt;

&lt;p&gt;One of the most noticeable examples is the German sharp s — ß. It is a lowercase character and, when converted to uppercase, it becomes double S - SS. That transformation is not reversible - SS becomes ss. See it &lt;a href="https://goo.gl/5EsXTi" rel="noopener noreferrer"&gt;online&lt;/a&gt;. TL;DR Unicode 5.1 introduced ẞ (LATIN CAPITAL LETTER SHARP S) but it is not generally considered an uppercase of ß in terms of character mapping. It was recently (in 2016) added to the &lt;a href="http://www.rechtschreibrat.com/DOX/rfdr_Bericht_2011-2016.pdf#8" rel="noopener noreferrer"&gt;German orthography ruleset&lt;/a&gt; as an equally valid form of SS.&lt;/p&gt;

&lt;p&gt;Many other lowercase ligatures do not have their corresponding precomposed uppercase forms. The complete list can be found in the &lt;a href="ftp://unicode.org/Public/UNIDATA/SpecialCasing.txt"&gt;Unicode Special Casing documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The double or triple problems
&lt;/h3&gt;

&lt;p&gt;Some uppercase characters are missing, so what? Ligatures can consist of 2 or even 3 characters, so uppercased text may be 3 times longer than the original lowercase. This is fact is extremely important where the resulting text length is limited. For example, in avatars or initials generators, like in &lt;a href="https://discuss.bitrise.io/t/text-in-app-avatar-not-always-confined-within-bounds/1934" rel="noopener noreferrer"&gt;this bug on bitrise.io&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The myσteriouς third case
&lt;/h3&gt;

&lt;p&gt;The Greek alphabet contains the Sigma letter which looks like this in uppercase: Σ. What is its lowercase form? Well, it depends! Usually, it is σ (non-final) but, at the end of the words, it's ς (final). However, if a Sigma is the only letter or the word is written in all caps then a non-final version is always used, even at the final position. See &lt;a href="https://goo.gl/nqqyGV" rel="noopener noreferrer"&gt;interactive example&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yet another edge case
&lt;/h3&gt;

&lt;p&gt;What is the lowercase of a &lt;a href="http://www.fileformat.info/info/unicode/char/0128/index.htm" rel="noopener noreferrer"&gt;Latin capital letter i with tilde&lt;/a&gt; Ĩ? As you may have guessed, the answer is not so trivial. A corresponding &lt;a href="http://www.fileformat.info/info/unicode/char/0129/index.htm" rel="noopener noreferrer"&gt;lowercase&lt;/a&gt; form exists. Both forms are dotless but it is perfectly normal. Both i and j do not have dots if they have some diacritic(s) attached. So what is the problem here?&lt;/p&gt;

&lt;p&gt;Apart from Turkish, Lithuanian ortographic rules are also exceptional in the case of the I letter. In the latter, the dot is preserved beneath the accent. This means, for example, that the aforementioned Ĩ, when lowercased in context of the Lithuanian language, becomes i̇̃. If you look carefully you can see that there are 3 characters: a &lt;a href="http://www.fileformat.info/info/unicode/char/0069/index.htm" rel="noopener noreferrer"&gt;Latin small letter i&lt;/a&gt;, a &lt;a href="http://www.fileformat.info/info/unicode/char/0307/index.htm" rel="noopener noreferrer"&gt;combining dot above&lt;/a&gt; and a &lt;a href="http://www.fileformat.info/info/unicode/char/0303/index.htm" rel="noopener noreferrer"&gt;combining tilde above&lt;/a&gt;. The length of the text has increased 3 times (again).&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep plꜽing with ligatures and multigraphs
&lt;/h3&gt;

&lt;p&gt;How can you write a word consisting of 7 letters, using only 6 characters? Just use precomposed ligatures and multigraphs (digraphs, trigraphs and so on)! Of course there is no precomposed character for each possible combination of joined letters. However, existent ones can be used to effectively increase text length limits. For example, a Silesian word &lt;em&gt;dzbonek&lt;/em&gt; (a pot) consists of 7 letters but it can be written as &lt;em&gt;ǳbonek&lt;/em&gt; using only 6 characters. See it &lt;a href="https://goo.gl/M4wx4a" rel="noopener noreferrer"&gt;online&lt;/a&gt;. Note that ǳ is a &lt;a href="https://en.wikipedia.org/wiki/Digraph_(orthography)" rel="noopener noreferrer"&gt;digraph&lt;/a&gt;, not a ligature.&lt;/p&gt;

&lt;p&gt;Now you can, for example, tweet messages containing more than 140 characters! The list of precomposed Unicode digraphs and ligatures can be found &lt;a href="https://en.wikipedia.org/wiki/List_of_precomposed_Latin_characters_in_Unicode#Digraphs_and_ligatures" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Little known ways to alphabetical order
&lt;/h3&gt;

&lt;p&gt;The alphabetical order is usually taught at the beginning of primary school. A, B, C, D… and so on to Z. As easy as pie!&lt;/p&gt;

&lt;p&gt;Unfortunately, alphabetical order depends on language. Even positions of the basic Latin letters (without diacritics) may be different. For example, in &lt;a href="https://en.wikipedia.org/wiki/Estonian_orthography" rel="noopener noreferrer"&gt;Estonian&lt;/a&gt;, the letter Z is between S and T.&lt;/p&gt;

&lt;p&gt;The location of letters with diacritical marks is also not universal. There are several possible schemes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Before the corresponding base letter, like in Maltese: W, X, Ż, Z.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the corresponding base letter, like in Polish: A, Ą, B, C, Ć.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the end of the alphabet, like in Swedish: Z, Å, Ä.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the same position (for collation purposes) as the base letter, like in Hungarian: O=Ó.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that the same letter may be collated differently in various languages and may even differ in the same language, depending on context!. For example, in Slovak, an A with an umlaut is always located after A. However, in German, it may either have the same value as the non-umlauted version, be located after it or even be treated as A+E. More info about which way is used in which cases can be found &lt;a href="https://en.wikipedia.org/wiki/German_orthography#Sorting" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bread, cash register and casino
&lt;/h3&gt;

&lt;p&gt;It’s not only individual letters that are subject to collation. Multigraphs can also have their own rules. In Slovak, CH is collated between H and I. So, for example, the word &lt;em&gt;chlieb&lt;/em&gt; (a bread) will be collated after &lt;em&gt;hodina&lt;/em&gt; (an hour). On the other hand, in Polish that digraph is treated just like two separate letters - C and H - and thus have no special collation rules. See it &lt;a href="https://goo.gl/HSydYz" rel="noopener noreferrer"&gt;online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hungarian even has double digraphs and each of them have their own collation rules. This leads to many complicated cases. Let’s consider one possible example. We have the SZ digraph. It is collated after S. Its doubled version (SZ + SZ) is a SSZ. This means that the word &lt;em&gt;kaszinó&lt;/em&gt; (a casino) should be before &lt;em&gt;kassza&lt;/em&gt; (cash register). Normally Z is after S but here we have: K A SZ I in the first word and (an equivalent of) K A SZ SZ in the second.&lt;/p&gt;

&lt;p&gt;Furthermore, the same group of letters may or may not be a (double) digraph depending on the context. For example, aforementioned Slovak CH is treated as 2 separate letters C and H in some words e.g. &lt;em&gt;viachlas&lt;/em&gt; (a polyphony). Normally, in Hungarian, NNY = NY + NY, like in the word &lt;em&gt;mennybolt&lt;/em&gt; (a heaven). However, we also have a &lt;em&gt;tizennyolc&lt;/em&gt; (eighteen) where NNY = N + NY, so there is a single letter N and a single digraph NY.&lt;/p&gt;

&lt;h3&gt;
  
  
  ΤНΙЅ ІЅ NОТ WНΑТ ΥОՍ ТНІNΚ ІТ ΙЅ
&lt;/h3&gt;

&lt;p&gt;You may think that the headline above consist of only plain Latin letters. In fact, the vast majority of them are Greek, Cyrillic or Armenian capital letters. They are only the &lt;a href="https://en.wikipedia.org/wiki/Homoglyph" rel="noopener noreferrer"&gt;homoglyphs&lt;/a&gt; of some Latin letters.&lt;/p&gt;

&lt;p&gt;So A (Latin capital A) is not the same thing as Α (Greek capital Alpha) nor А (Cyrillic capital A). Why is this important? Due to the fact that they are indistinguishable, they can be used in &lt;a href="https://en.wikipedia.org/wiki/IDN_homograph_attack" rel="noopener noreferrer"&gt;IDN homograph attacks&lt;/a&gt;. For example, the domain bank.com, only containing Latin letters, looks pretty much the same as bаnk.com, containing the Cyrillic small A instead of Latin small A. Such domains may be used for phishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap up
&lt;/h3&gt;

&lt;p&gt;Dealing with text may be tricky in some cases — especially if you work in a multilingual environment. As a rule of thumb, all configurations should be appropriate for the given context. For example, the user’s current language should be taken into account when processing texts visible to these users, while machine-readable ones should be processed in the language-neutral way (or using English if it is not possible). Selected collation settings should match actual usage as well. Text should be normalized when needed and the chosen normalization method should be consistent across all the system.&lt;br&gt;
 Want to know about more edge cases? Stay tuned, part 2 is on the way!&lt;/p&gt;

</description>
      <category>android</category>
      <category>java</category>
      <category>kotlin</category>
      <category>trivia</category>
    </item>
    <item>
      <title>Edge Cases to Keep in Mind. Part 2 — Files</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Thu, 08 Aug 2024 17:27:13 +0000</pubDate>
      <link>https://dev.to/koral/edge-cases-to-keep-in-mind-part-2-files-53</link>
      <guid>https://dev.to/koral/edge-cases-to-keep-in-mind-part-2-files-53</guid>
      <description>&lt;p&gt;Did you know, that there may be a File which exists and doesn’t exist at the same time? Are you aware, that you can delete a file and still use it? Discover these &amp;amp; other files edge cases in software development.&lt;/p&gt;

&lt;p&gt;In my previous &lt;a href="https://www.thedroidsonroids.com/blog/edge-cases-to-keep-in-mind-part-1-text" rel="noopener noreferrer"&gt;article&lt;/a&gt; about edge cases in software development, I was writing about text traps and I gave you some suggestions, how to avoid them. In this blog post, I would like to focus on files and file I/O operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A File which is not a file
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/reference/java/io/File.html" rel="noopener noreferrer"&gt;java.io.File&lt;/a&gt; API provides, among others, these 3 methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/java/io/File.html#exists()" rel="noopener noreferrer"&gt;#exists()&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/java/io/File.html#isDirectory()" rel="noopener noreferrer"&gt;#isDirectory()&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/java/io/File.html#isFile()" rel="noopener noreferrer"&gt;#isFile()&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One may think that, if it is pointed by a given path that exists, an object is either a file or a directory — like in &lt;a href="https://stackoverflow.com/q/20523247/630398" rel="noopener noreferrer"&gt;this question&lt;/a&gt; on Stack Overflow. However, this is not always true.&lt;/p&gt;

&lt;p&gt;It is not explicitly mentioned in &lt;a href="https://developer.android.com/reference/java/io/File.html#isFile()" rel="noopener noreferrer"&gt;File#isFile()&lt;/a&gt; javadocs, but &lt;strong&gt;file **there really means **regular file&lt;/strong&gt;. Thus, &lt;a href="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_163" rel="noopener noreferrer"&gt;special Unix files&lt;/a&gt; like devices, sockets and pipes may exist but they are not &lt;strong&gt;files&lt;/strong&gt; in that definition.&lt;/p&gt;

&lt;p&gt;Look at the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.io.File

val file = File("/dev/null")
println("exists:      ${file.exists()}")
println("isFile:      ${file.isFile()}")
println("isDirectory: ${file.isDirectory()}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see on the &lt;a href="https://repl.it/LNWW/1" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;, a File which is neither a file nor a directory may exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  To exist, or not to exist?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_381" rel="noopener noreferrer"&gt;Symbolic links&lt;/a&gt; are also special files but they are treated transparently almost everywhere in (old) &lt;code&gt;java.io&lt;/code&gt; API. The only exception is the &lt;a href="https://developer.android.com/reference/java/io/File.html#getCanonicalPath()" rel="noopener noreferrer"&gt;#getCanonicalPath()/#getCanonicalFile()&lt;/a&gt; methods family. Transparency here means that all the operations are forwarded to the target, just like they are performed directly on it. Such transparency is usually useful, e.g. you can just read from, or write to, some file. You don’t care about the optional link path resolution. However, it may also lead to some strange cases. For example, there may be a File which exists and doesn’t exist at the same time.&lt;/p&gt;

&lt;p&gt;Let’s consider a dangling symbolic link. Its target does not exist, so all the methods from the previous section will return false. Nonetheless, the source file path is still occupied, e.g. you cannot create a new file on that path. Here is the code demonstrating this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

val path = Paths.get("foo")
Files.createSymbolicLink(path, path)
println("exists       : ${path.toFile().exists()}")
println("isFile       : ${path.toFile().isFile()}")
println("isDirectory  : ${path.toFile().isDirectory()}")
println("createNewFile: ${path.toFile().createNewFile()}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;a href="https://repl.it/LN4s/1" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order matters
&lt;/h2&gt;

&lt;p&gt;In java.io API, to create a possibly non-existent directory and ensure that it exists afterwards, you can use &lt;a href="https://developer.android.com/reference/java/io/File.html#mkdir()" rel="noopener noreferrer"&gt;File#mkdir()&lt;/a&gt; (or &lt;a href="https://developer.android.com/reference/java/io/File.html#mkdirs()" rel="noopener noreferrer"&gt;File#mkdirs()&lt;/a&gt; if you want to create non-existent parent directories as well) and then &lt;a href="https://developer.android.com/reference/java/io/File.html#isDirectory()" rel="noopener noreferrer"&gt;File#isDirectory()&lt;/a&gt;. It is important to use these methods in the mentioned order. Let’s see what may happen if the order is reversed. Two (or more) threads performing the same operations are needed to demonstrate this case. Here, we’ll use blue and red threads.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;(red) isDirectory()? — no, need to create&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(blue) isDirectory()? — no, need to create&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(red) mkdir()? — success&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(blue) mkdir()? — fail&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see a blue thread failed to create a directory. However, it was in fact created, so the result should be positive. If &lt;code&gt;isDirectory()&lt;/code&gt; had called at the end, the result would always have been correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden limitation
&lt;/h2&gt;

&lt;p&gt;The number of files open at the same time by a given UNIX process is limited to the value of &lt;a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html" rel="noopener noreferrer"&gt;RLIMIT_NOFILE&lt;/a&gt;. On Android, this is usually 1024 but effectively (excluding file descriptors used by the framework) you can use even less (during tests with empty Activity on Android 8.0.0, there were approximately 970 file descriptors available to use). What happens if you try to open more? Well, the file won’t be opened. Depending on the context, you may encounter an exception with an explicit reason (&lt;em&gt;Too many open files&lt;/em&gt;), a little bit of an enigmatic message (e.g. &lt;em&gt;This file can not be opened as a file descriptor; it is probably compressed&lt;/em&gt;) or just &lt;code&gt;false&lt;/code&gt; as a return value when you normally expect &lt;code&gt;true&lt;/code&gt;. See the code demonstrating these issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package pl.droidsonroids.edgetest

import android.content.res.AssetFileDescriptor
import android.support.test.InstrumentationRegistry
import org.junit.Assert
import org.junit.Test

class TooManyOpenFilesTest {
    //asset named "test" required
    @Test
    fun tooManyOpenFilesDemo() {
        val context = InstrumentationRegistry.getContext()
        val assets = context.assets
        val descriptors = mutableListOf&amp;lt;AssetFileDescriptor&amp;gt;()
        try {
            for (i in 0..1024) {
                descriptors.add(assets.openFd("test"))
            }
        } catch (e: Exception) {
            e.printStackTrace() //java.io.FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed
        }
        try {
            context.openFileOutput("test", 0)
        } catch (e: Exception) {
            e.printStackTrace() //java.io.FileNotFoundException: /data/user/0/pl.droidsonroids.edgetest/files/test (Too many open files)
        }

        val sharedPreferences = context.getSharedPreferences("test", 0)
        Assert.assertTrue(sharedPreferences.edit().putBoolean("test", true).commit())
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that, if you use &lt;a href="https://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()" rel="noopener noreferrer"&gt;#apply()&lt;/a&gt;, the value will just not be saved persistently — so you won’t get any exception. However, it will be accessible until the app process holding that SharedPreferences instance is killed. That’s because shared preferences are also saved in the memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Undeads really exists
&lt;/h2&gt;

&lt;p&gt;One may think that zombies, ghouls and other similar creatures exist in fantasy and horror fiction only. But… they are real in computer science! Such common terms refer to the &lt;a href="https://en.wikipedia.org/wiki/Zombie_process" rel="noopener noreferrer"&gt;zombie processes&lt;/a&gt;. In fact, undead files can also be easily created.&lt;/p&gt;

&lt;p&gt;In Unix-like operating systems, file deletion is usually implemented by &lt;a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/unlink.html" rel="noopener noreferrer"&gt;unlinking&lt;/a&gt;. The unlinked file name is removed from the file system (assuming that it is the last hardlink) but any already open file descriptors remain valid and usable. You can still read from and write to such a file. Here is the snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import java.io.BufferedReader
import java.io.File
import java.io.FileReader

val file = File("test")
file.writeText("this is file content")

BufferedReader(FileReader(file)).use {
   println("deleted?: ${file.delete()}")
   println("content?: ${it.readLine()}")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;a href="https://repl.it/L91G/0" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;First of all, remember that we can’t forget about the proper method calling order when creating non-existent directories. Furthermore, keep in mind that a number of files open at the same time is limited and not only files explicitly opened by you are counted. And the last, but not least, a trick with file deletion before the last usage can give you a little bit more flexibility.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/edge-cases-to-keep-in-mind-part-2-files" rel="noopener noreferrer"&gt;www.thedroidsonroids.com&lt;/a&gt; on September 27, 2017.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>java</category>
      <category>kotlin</category>
      <category>triva</category>
    </item>
    <item>
      <title>Edge Cases to Keep in Mind. Part 3 — Time of Check to Time of Use Race Conditions in Android UI</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Thu, 08 Aug 2024 17:18:05 +0000</pubDate>
      <link>https://dev.to/koral/edge-cases-to-keep-in-mind-part-3-time-of-check-to-time-of-use-race-conditions-in-android-ui-4k47</link>
      <guid>https://dev.to/koral/edge-cases-to-keep-in-mind-part-3-time-of-check-to-time-of-use-race-conditions-in-android-ui-4k47</guid>
      <description>&lt;p&gt;In this article, we’ll show how race conditions affect Android runtime permission system.&lt;/p&gt;

&lt;p&gt;If you are a developer you’ve probably heard about &lt;a href="https://en.wikipedia.org/wiki/Race_condition#Software" rel="noopener noreferrer"&gt;race conditions&lt;/a&gt;. They are often associated with concurrent background operations performed in the fractions of seconds. However, certain race conditions may also appear in UI and last for the infinite time. In this article, we’ll show how race conditions affect Android runtime permission system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Race condition &amp;amp; Time of check to time of use — what does it mean?
&lt;/h3&gt;

&lt;p&gt;Firstly, we need to explain some basic terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race condition&lt;/strong&gt; occurs if multiple operations occur at the same time and their order affects the result. A textbook example is two threads incrementing the same variable. It seems to be trivial, however, usually, we need to use special, thread-safe elements to implement it properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time of check to time of use&lt;/strong&gt; (TOCTTOU or TOCTOU, pronounced &lt;em&gt;TOCK too&lt;/em&gt;) is &lt;strong&gt;a specific kind of race condition where a performed operation is preceded by state checking and that state is modified in the time between a check and actual execution&lt;/strong&gt;. It is often illustrated by checking user privileges at the login time only. For example, if you are an administrator at the moment when you sign in and you can use your privileges until you sign out, even if your admin access is revoked in the meantime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Android runtime permissions
&lt;/h3&gt;

&lt;p&gt;Let’s also summarise Android &lt;a href="https://developer.android.com/training/permissions/requesting.html" rel="noopener noreferrer"&gt;runtime permissions&lt;/a&gt; basics.&lt;/p&gt;

&lt;p&gt;Starting from Android 6.0 (API level 23) the most dangerous permissions has to be explicitly granted by a user at runtime rather than all at once on app installation time. The most noticeable element here is a system dialogue with &lt;code&gt;DENY&lt;/code&gt; and &lt;code&gt;ALLOW&lt;/code&gt; buttons like shown in figure 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8ftrzsvt0vpwzrflaob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8ftrzsvt0vpwzrflaob.png" alt="Runtime permission dialog" width="270" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 1. Runtime permission dialog&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After clicking on &lt;code&gt;DENY&lt;/code&gt; button we receive &lt;a href="https://developer.android.com/reference/android/content/pm/PackageManager.html#PERMISSION_DENIED" rel="noopener noreferrer"&gt;PERMISSION_DENIED&lt;/a&gt; in &lt;code&gt;onRequestPermissionsResult&lt;/code&gt; callback and we should &lt;em&gt;disable the functionality that depends on this permission&lt;/em&gt;. According to the &lt;a href="https://developer.android.com/training/permissions/requesting.html#handle-response" rel="noopener noreferrer"&gt;official snippet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, user can also grant or deny permissions by using &lt;strong&gt;App permissions&lt;/strong&gt; screen in application settings. You can see that screen in figure 2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7juw6ldxmbn7wpgsxuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7juw6ldxmbn7wpgsxuk.png" alt="App permissions screen" width="270" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 2. App permissions screen&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Edge cases are everywhere
&lt;/h3&gt;

&lt;p&gt;Most of you may think that runtime permission denial is a super simple feature and there is no element which can be broken. Well, nothing could be further from the truth!&lt;/p&gt;

&lt;p&gt;A dialogue appears only if permission is not granted. So we have the &lt;strong&gt;time of check&lt;/strong&gt; just before displaying a dialogue. And &lt;strong&gt;time of use&lt;/strong&gt; when &lt;code&gt;DENY&lt;/code&gt; button is clicked. A period between them can last forever - user can open a dialogue then press home or recent button to move the task with the app to background and return anytime later.&lt;/p&gt;

&lt;p&gt;Let’s check if runtime permission dialogue is vulnerable to TOCTTOU. To do so, we can create a super simple activity which checks actual granted permissions after returning from the dialog. Note that apart from standard &lt;code&gt;onRequestPermissionsResult&lt;/code&gt; arguments check we'll call &lt;a href="https://developer.android.com/reference/android/content/Context.html#checkSelfPermission(java.lang.String)" rel="noopener noreferrer"&gt;Context#checkSelfPermission()&lt;/a&gt; to obtain &lt;strong&gt;current&lt;/strong&gt; permission grant status. Don't forget to set &lt;code&gt;targetSdkVersion&lt;/code&gt; to 23 or higher. The code should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), 1)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array&amp;lt;out String&amp;gt;, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        val checkResultTextView = findViewById&amp;lt;TextView&amp;gt;(R.id.grantResultTextView)
        val grantResultTextView = findViewById&amp;lt;TextView&amp;gt;(R.id.checkResultTextView)

        val checkPermissionResult = checkSelfPermission(WRITE_EXTERNAL_STORAGE).toPermissionResult()
        val grantPermissionResult = grantResults.firstOrNull()?.toPermissionResult()
        checkResultTextView.text = "checkSelfPermission: $checkPermissionResult"
        grantResultTextView.text = "onRequestPermissionsResult: $grantPermissionResult"
    }

    private fun Int.toPermissionResult() = when (this) {
        PERMISSION_GRANTED -&amp;gt; "granted"
        PERMISSION_DENIED -&amp;gt; "denied"
        else -&amp;gt; "unknown"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can perform a test. To do that we need a device or AVD with Android 6.0 (API 23) or newer. Test result is shown on figure 3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoik2xq1nhhh95cg4lfc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsoik2xq1nhhh95cg4lfc.gif" alt="Captured TOCTTOU" width="270" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 3. Captured TOCTTOU&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can see that results differs. &lt;code&gt;onRequestPermissionsResult&lt;/code&gt; argument is not valid. So &lt;code&gt;DENY&lt;/code&gt; button is not denying anything! It simply does nothing with permission status but returns denied results to app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap up
&lt;/h3&gt;

&lt;p&gt;It is important to keep timing into account when checking various things in the code. Caching check results may cause bugs and weird effects. TOCTTOU vulnerability does not depend on either platform or programming language so it has been classified as &lt;a href="https://cwe.mitre.org/data/definitions/367.html" rel="noopener noreferrer"&gt;CWE-367&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check the full source code on &lt;a href="https://github.com/DroidsOnRoids/android-runtime-permission-tock-too" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
The project also contains automated UI test demonstrating the issue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/time-of-check-to-time-of-use-race-conditions-in-android-ui" rel="noopener noreferrer"&gt;www.thedroidsonroids.com&lt;/a&gt; on December 14, 2017.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>java</category>
      <category>kotlin</category>
      <category>trivia</category>
    </item>
    <item>
      <title>Edge Cases in App and Backend Development — Dates &amp; Times</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Thu, 08 Aug 2024 17:11:13 +0000</pubDate>
      <link>https://dev.to/koral/edge-cases-in-app-and-backend-development-dates-times-16o5</link>
      <guid>https://dev.to/koral/edge-cases-in-app-and-backend-development-dates-times-16o5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You may think that dealing with dates and time is easy. We have a minute that lasts 60 seconds, an hour with 60 minutes, a day with 24 hours, a week with 7 days, a month with 28 to 31 days, and so on.&lt;/p&gt;

&lt;p&gt;Surely no rocket science is required here…&lt;/p&gt;

&lt;p&gt;Well, nothing could be further from the truth!&lt;/p&gt;

&lt;p&gt;We will show the traps and pitfalls related to date and time that you may encounter during application and &lt;a href="https://www.thedroidsonroids.com/blog/mobile-app-backend-development-guide-for-app-owners" rel="noopener noreferrer"&gt;backend development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurement units
&lt;/h2&gt;

&lt;p&gt;Let’s start with the units of measurement, from the smallest ones to the largest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seconds and milliseconds
&lt;/h2&gt;

&lt;p&gt;The smallest unit used everyday is a second. It is also the base of &lt;a href="https://www.unixtimestamp.com/" rel="noopener noreferrer"&gt;Unix time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, in some programming languages, such as Java, the most common unit is a millisecond (1/1000 of the second), as by the &lt;a href="https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#currentTimeMillis()" rel="noopener noreferrer"&gt;System.currentTimeMillis()&lt;/a&gt; method, for example.&lt;/p&gt;

&lt;p&gt;That divergence may lead to many errors.&lt;/p&gt;

&lt;p&gt;If you receive the numeric value from outside your system, it might not at first glance be clear what unit of measurement it uses, even after reading the documentation!&lt;/p&gt;

&lt;p&gt;Look at the &lt;code&gt;DATE&lt;/code&gt; field in the &lt;a href="https://developer.android.com/reference/kotlin/android/provider/Telephony.TextBasedSmsColumns?hl=en#DATE:kotlin.String" rel="noopener noreferrer"&gt;SMS&lt;/a&gt; and &lt;a href="https://developer.android.com/reference/android/provider/Telephony.BaseMmsColumns#DATE" rel="noopener noreferrer"&gt;MMS&lt;/a&gt; content provider (database) columns. Both docs say only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The date the message was received.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type: INTEGER (long)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the SMS uses milliseconds while the MMS uses seconds. Surprised? Well, it may happen, especially if such APIs are designed by different people.&lt;/p&gt;

&lt;p&gt;How can you deal with cases like these? And how can you avoid them?&lt;/p&gt;

&lt;p&gt;Fortunately, we can formulate several general rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always ensure the format of incoming data.&lt;/strong&gt;&lt;br&gt;
Check it in the wild because the documentation may be incorrect and/or outdated. It is usually very easy to spot an error, such as a date 50 thousand years too far in the future, when you look for it while developing. This result occurs when milliseconds are treated as seconds, for example.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you have any influence on the side which is sending the values (eg. the system is being designed and nobody uses it yet), consider the standardized textual formats.&lt;/strong&gt;&lt;br&gt;
Here, I emphasize standardization (eg.&lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;ISO 8601&lt;/a&gt;) not some custom formats (we will broaden this topic later). After a few years, nobody from the original team may longer work for that project, and the textual format is straightforward to understand for new developers, as they don’t need to look at the docs.&lt;br&gt;
Numerical formats may be better, however, in performance-critical appliances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;**Use dedicated classes for dealing with date/time/duration values rather than raw integers.&lt;br&gt;
**In the Java world, we have a &lt;code&gt;java.time&lt;/code&gt; package with a lot of useful classes like &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html" rel="noopener noreferrer"&gt;Instant&lt;/a&gt; or &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html" rel="noopener noreferrer"&gt;Duration&lt;/a&gt;. If there is an integer called eg. &lt;code&gt;eventDuration&lt;/code&gt;, it is not known whether it stores seconds or milliseconds - or maybe even days?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you must deal with raw values (integers, longs etc.) which you could not just wholly refactor, such as legacy code, &lt;strong&gt;include the unit of measurement with the variables/fields names&lt;/strong&gt;.&lt;br&gt;
For example,eventDurationSeconds is unambiguous.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Leap seconds
&lt;/h3&gt;

&lt;p&gt;You’ve probably heard about leap years. They differ from “normal” ones by being an extra day longer. We also have leap seconds!&lt;/p&gt;

&lt;p&gt;Are they longer than non-leap seconds?&lt;/p&gt;

&lt;p&gt;Well, it depends!&lt;/p&gt;

&lt;p&gt;First, let’s start with a little bit of theory. The Earth is slowing down; really it is not a philosophical statement but a scientifically proven fact. It is called &lt;a href="https://en.wikipedia.org/wiki/%CE%94T_(timekeeping)" rel="noopener noreferrer"&gt;delta-T&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OK, so what does this mean in practice for us? Well, our units of measurement for time have a standardized length. The base unit is a &lt;a href="https://en.wikipedia.org/wiki/Second" rel="noopener noreferrer"&gt;second&lt;/a&gt;, which is defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The time duration of 9 192 631 770 periods of the radiation corresponding to the transition between the two &lt;a href="https://en.wikipedia.org/wiki/Hyperfine_structure" rel="noopener noreferrer"&gt;hyperfine levels&lt;/a&gt; of the fundamental unperturbed &lt;a href="https://en.wikipedia.org/wiki/Ground_state" rel="noopener noreferrer"&gt;ground-state&lt;/a&gt; of the &lt;a href="https://en.wikipedia.org/wiki/Caesium-133" rel="noopener noreferrer"&gt;caesium-133&lt;/a&gt; atom. (source: &lt;a href="https://www.bipm.org/utils/common/pdf/si-brochure/SI-Brochure-9.pdf" rel="noopener noreferrer"&gt;bipm.org)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That duration is constant, and informs all other units derived from seconds eg. a minute consists of 60 seconds, an hour is 60 minutes (3,600 seconds) and so on.&lt;/p&gt;

&lt;p&gt;However, if the Earth slows down (and the day gets longer), we have to somehow accommodate that slowdown to ensure the time measured by our units is consistent with reality. This is done by inserting extra seconds — the &lt;a href="https://en.wikipedia.org/wiki/Leap_second#International_proposals_for_elimination_of_leap_seconds" rel="noopener noreferrer"&gt;leap seconds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are only 2 available slots for leap seconds in the year: the very end of June and December. The very end means the last day (30th or 31st respectively) just after 23:59:59 UTC (so local time varies).&lt;/p&gt;

&lt;p&gt;After those normally last seconds, the additional leap second is inserted before moving to the next day. So, we can have 23:59:60 (61 seconds in a minute — as we count from‌ 0).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxq4rpagq984fc3dsjr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxq4rpagq984fc3dsjr2.png" alt="A leap second" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Due to the fact that the slowdown is not constant, the leap seconds are inserted irregularly. The latest one (at the time of writing this article in April 2021) occurred in December 2016, which was more than 4 years ago. However, the penultimate one was in June 2015, with only a 1.5-year difference between the two.&lt;/p&gt;

&lt;p&gt;In some of the places where we can set the time — like physical wall clocks or some framework APIs, there may be no ability to observe and/or set the time with the leap second.&lt;/p&gt;

&lt;p&gt;For example the old-fashioned &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html" rel="noopener noreferrer"&gt;Date&lt;/a&gt; Java class supports even double leap seconds — the range spreads from 0 to 61, so 62 seconds are possible!&lt;/p&gt;

&lt;p&gt;However, the modern &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html" rel="noopener noreferrer"&gt;Instant&lt;/a&gt; class from the &lt;code&gt;java.time&lt;/code&gt; package does not expose leap seconds to programmers. Leap second is stretched equally over the last 1,000 seconds of the day (those seconds are longer).&lt;/p&gt;

&lt;p&gt;Note that, in theory, the leap second can be also negative. But it has not happened so far.&lt;/p&gt;

&lt;p&gt;This means that a minute could consist of 59 seconds, which may lead to huge issues. For example, if some action is scheduled to occur at 23:59:59.001 and it turns out that desired time does not exist…&lt;/p&gt;

&lt;p&gt;Fortunately analogously to spreading the visible seconds may also be shrunk being completely transparent to programmers.&lt;/p&gt;

&lt;p&gt;We know that the 61tst second can exist, but what about the 62nd? Well, &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/Date.html" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It is extremely unlikely that two leap seconds will occur in the same minute, but this specification follows the date and time conventions for ISO C.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indeed, in the &lt;a href="https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html" rel="noopener noreferrer"&gt;C specification&lt;/a&gt; we have a [0, 61] range. But why? The same question bothered Paul Eggert, the author of &lt;a href="https://www.iana.org/time-zones" rel="noopener noreferrer"&gt;tzdata&lt;/a&gt; (timezone database) in 1992. As we can read in the archive:&lt;/p&gt;

&lt;p&gt;*“Because there really were two leap seconds in one year (on separate days), and someone on the ANSI C committee thought that they came on the same day.” *— source &lt;a href="https://groups.google.com/g/comp.protocols.time.ntp/c/HvZBLs413ng" rel="noopener noreferrer"&gt;groups.google.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That slight interpretation error, which occurred several decades ago, is visible still now because the new implementation needs to be backwards compatible with these standards.&lt;/p&gt;

&lt;p&gt;Let’s go to other edge cases in app and backend development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The days
&lt;/h2&gt;

&lt;p&gt;The minutes and hours do not involve any unexpected cases so let’s jump to the days.&lt;/p&gt;

&lt;p&gt;What is a day? It depends! You can say that it lasts 24 hours from 00:00:00 to 23:59:60 (including the leap second 🙂 ). Well, whether the latter is generally true, the day does not always last 24 hours, as it may be 23, 25 or even 21 and 27! Why those values? The answer is…&lt;/p&gt;

&lt;h2&gt;
  
  
  Daylight saving time
&lt;/h2&gt;

&lt;p&gt;So-called &lt;a href="https://en.wikipedia.org/wiki/Daylight_saving_time" rel="noopener noreferrer"&gt;DST&lt;/a&gt; or “summer time” advances the clocks in warmer months intended for reducing power consumption. Darkness begins later than in “regular” (non-DST) time. Nowadays, the necessity of DST is debatable because there are a lot of disadvantages. Some countries have even stopped observing DST recently (eg. &lt;a href="https://en.wikipedia.org/wiki/Summer_time_in_Europe#Russia" rel="noopener noreferrer"&gt;Russia&lt;/a&gt; in 2014) due to them.&lt;/p&gt;

&lt;p&gt;What are the problems with DST? Let’s see!&lt;/p&gt;

&lt;p&gt;I won’t cover things like forgotten manual adjustments of wall clocks or delayed public transportation but, instead, I will focus on aspects related to &lt;a href="https://www.thedroidsonroids.com/blog/mobile-app-backend-development-guide-for-app-owners" rel="noopener noreferrer"&gt;backend&lt;/a&gt; and &lt;a href="https://www.thedroidsonroids.com/blog/what-is-a-mobile-app-app-development-basics-for-businesses" rel="noopener noreferrer"&gt;app development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You may think that, in the summer time, we advance the clocks by 1 hour. This is not always the case! There are places in the world where the difference is 3 hours (like &lt;a href="https://www.timeanddate.com/worldclock/antarctica/casey" rel="noopener noreferrer"&gt;Casey&lt;/a&gt;) or 30 minutes (like &lt;a href="https://www.timeanddate.com/worldclock/australia/lord-howe" rel="noopener noreferrer"&gt;Lord Howe Island&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Summer time, not exactly as the name suggests, can also cover some part of the spring or autumn but, generally, it is related to the warmer months. In turn, that depends on the hemisphere.&lt;/p&gt;

&lt;p&gt;While in Europe there is a summer, in Australia there is a winter and vice versa. So, to put that in perspective, Australian summer time occurs during Europe’s winter!&lt;/p&gt;

&lt;p&gt;What’s more, the definition of summer in terms of time depends on jurisdiction. In all the countries of the European Union, time is changed at the same moment so the time difference between Berlin and London, for example, is always 1 hour, no matter whether we are in summer or winter.&lt;/p&gt;

&lt;p&gt;But let’s consider the time difference between Sydney and London. In this case, it depends on the day of the year! That’s because Sydney starts and stops observing DST at different dates than London.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo723ce6m6apuspv6qhff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo723ce6m6apuspv6qhff.png" alt="Time difference between London and Sydney" width="800" height="523"&gt;&lt;/a&gt;&lt;br&gt;
Inspiration: &lt;a href="https://www.timeanddate.com/time/zone/australia/sydney" rel="noopener noreferrer"&gt;timeanddate.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, starting in January we have 10 hours difference. Sydney observes DST at that time but London does not. At the end of March, London starts observing DST, while the state in Sydney remains unchanged, so the difference reduces to 9 hours. Then in April, Sydney stops observing DST, so we have 8 hours. We have 3 different offsets. So the question about the time difference between Sydney and London has 3 answers!&lt;/p&gt;

&lt;p&gt;Countries like the USA, EU members or Australia have, let’s say, stable jurisdictions with respect to DST. It is well known in advance when the transition occurs. However, it is not always the case, as some countries may change the laws unexpectedly. For example in 2020 in Fiji, &lt;a href="https://mm.icann.org/pipermail/tz/2020-October/029317.html" rel="noopener noreferrer"&gt;the rules have changed&lt;/a&gt; with the announcement only arriving a few months before.&lt;/p&gt;

&lt;p&gt;Even more complicated situations occur in some Islamic countries that officially observe &lt;a href="https://en.wikipedia.org/wiki/Ramadan" rel="noopener noreferrer"&gt;Ramadan&lt;/a&gt;. If they also observe DST, the latter may be… suspended (for the period of Ramadan).&lt;/p&gt;

&lt;p&gt;That means the DST transition may occur more than once (back and forth) and even a few times in a year. Furthermore, Ramadan is calculated according to the Islamic (lunar) calendar. Prediction is used to calculate Ramadan boundary dates and it may not be always accurate. For example, in Palestine in 2020, &lt;a href="https://mm.icann.org/pipermail/tz-announce/2020-October/000062.html" rel="noopener noreferrer"&gt;the prediction&lt;/a&gt; turns out to be short by about a week. Changes were applied only a few days in advance.&lt;/p&gt;

&lt;p&gt;Most of the systems use the &lt;a href="https://www.iana.org/time-zones" rel="noopener noreferrer"&gt;IANA Time Zone&lt;/a&gt; database as the source of DST transition moments. There are usually a few updates each year in that database.&lt;/p&gt;

&lt;p&gt;Note that dealing with DST may have an impact on algorithms. Let’s consider a few typical scenarios.&lt;/p&gt;

&lt;p&gt;If we have an alarm scheduled to a particular wall time (eg. 02:30) it may turn out that such a moment does not exist (when time is changed from 02:00 to 03:00 during DST transition) or it exists twice when the time is changed backwards. If, for example, the backup won’t be done or medicates won’t be given or will be given twice, then the consequences may be terrible.&lt;/p&gt;

&lt;p&gt;Another common effect consists of cyclical triggering time changes if the user is located in the timezone observing DST. For example, &lt;a href="https://discuss.bitrise.io/t/build-schedule-has-changed-on-dst-transition/8618/5" rel="noopener noreferrer"&gt;if you schedule a build on bitrise.io&lt;/a&gt; to be fired at 10:00, it may suddenly start firing at 11:00.&lt;/p&gt;

&lt;p&gt;This happens because the logic under the hood is not aware of DST and the time visible to the user is only calculated when rendering the UI. The absolute moment in time is always the same but the time visible to the user changes depending on DST. Usually, it is not what customers expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The weeks
&lt;/h2&gt;

&lt;p&gt;What is the first day of the week? If you live in Europe, you would probably say Monday. In the USA it will be Sunday and in Arabic countries — Saturday. These facts may impact algorithm constructing and/or rendering calendars.&lt;/p&gt;

&lt;p&gt;What about the week number in the year? You may think that everything starts from January 1st.&lt;/p&gt;

&lt;p&gt;As you might have guessed, this is also not always the case!&lt;/p&gt;

&lt;p&gt;According to the ISO standard, the 1st &lt;a href="https://en.wikipedia.org/wiki/ISO_week_date" rel="noopener noreferrer"&gt;week of the year&lt;/a&gt; must contain Thursday. For example, in 2021 January 1st is on Friday so it belongs to the last week of the previous year. The first week of 2021 started on January 4th! &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/WeekFields.html#getMinimalDaysInFirstWeek--" rel="noopener noreferrer"&gt;Not all the locales&lt;/a&gt; use the same rule as the ISO standard in that matter, however.&lt;/p&gt;

&lt;h2&gt;
  
  
  The months
&lt;/h2&gt;

&lt;p&gt;The Gregorian calendar used by most of the world has 12 months, from January to December. However, other kinds of calendar may have more months. For example the &lt;a href="https://en.wikipedia.org/wiki/Hebrew_calendar" rel="noopener noreferrer"&gt;Hebrew calendar&lt;/a&gt; may have 13 months. The 13th one (in the IT world) is called &lt;a href="https://en.wikipedia.org/wiki/Undecimber" rel="noopener noreferrer"&gt;Undecimber&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The years
&lt;/h2&gt;

&lt;p&gt;Usually, the year lasts 365 days. However, each one divisible by 4 (eg. 2020, 2024 etc.) is a leap year which has 366 days (with 29 days in February instead of the normal 28). But, if it is divisible by 100 (2100, 2200 etc.) it is NOT a leap year. But 🙂 if it is divisible by 400 (2000, 2400 etc.) it is a leap year.&lt;/p&gt;

&lt;p&gt;Fortunately, you don’t have to (and should not!) try to implement such distinctions yourself. You should use date classes/functions well known libraries of the given programming language.&lt;/p&gt;

&lt;p&gt;There were many spectacular bugs in well-known services related to incorrect leap year calculations. There is even a term: &lt;a href="https://en.wikipedia.org/wiki/Leap_year_problem" rel="noopener noreferrer"&gt;Leap year problem&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timezones
&lt;/h2&gt;

&lt;p&gt;The local time depends on the longitude. While it’s noon in a particular location, it’s also midnight on the other side of the globe.&lt;/p&gt;

&lt;p&gt;It is impractical to adjust the clocks continuously when traveling, so the globe was divided into zones which cover the areas having the same local official time.&lt;/p&gt;

&lt;p&gt;Zone boundaries are usually equal to country boundaries in the case of small countries or some geographical regions in the biggest ones (like Australia, USA or Russia).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-dppbk.nitrocdn.com%2FyQqYxSaTIazRSTSDbfxPrqFzJPJOhsTG%2Fassets%2Fimages%2Foptimized%2Frev-3566a21%2Fwww.thedroidsonroids.com%2Fwp-content%2Fuploads%2F2021%2F04%2FZrzut-ekranu-2021-04-26-o-12.06.12-940x538.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-dppbk.nitrocdn.com%2FyQqYxSaTIazRSTSDbfxPrqFzJPJOhsTG%2Fassets%2Fimages%2Foptimized%2Frev-3566a21%2Fwww.thedroidsonroids.com%2Fwp-content%2Fuploads%2F2021%2F04%2FZrzut-ekranu-2021-04-26-o-12.06.12-940x538.png" alt="Timezones across the world" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://www.timeanddate.com/time/map/" rel="noopener noreferrer"&gt;timeanddate.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Due to political and economical reasons in some places, the official time varies significantly from the “legitimate” one (taking only longitude/sun time into account). For example, in Spain, the zone is the same as in most of continental central Europe rather than the United Kingdom, which is closer according to the longitude.&lt;/p&gt;

&lt;p&gt;Most time zones have integral offsets (the difference from UTC — a standard time) eg. in Berlin +1 hour (+2 in DST) or -3 h in Buenos Aires (Argentina, no DST). However, the offset may include halves of hours eg. in Mumbai (India) we have +5:30h (no DST).&lt;/p&gt;

&lt;p&gt;If that wasn’t enough, quarters are also possible, as in Kathmandu (Nepal) we have +5:45h!&lt;/p&gt;

&lt;p&gt;Fortunately, there are no more finer-grained offsets at the time of writing. However, they used to exist in the past, such as &lt;a href="https://en.wikipedia.org/wiki/UTC%2B00:20" rel="noopener noreferrer"&gt;+0:20 in the Netherlands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Due to the fact that we have 24 hours on the clocks, one may think that it is also a range of possible offsets. +12:00 and -12:00 combined together gives 24.&lt;/p&gt;

&lt;p&gt;However, the farthest time distance between time zones is 26 hours!&lt;/p&gt;

&lt;p&gt;This is possible because we have a &lt;a href="https://en.wikipedia.org/wiki/UTC%2B14:00" rel="noopener noreferrer"&gt;+14:00&lt;/a&gt; offset. It was adopted by several countries in Oceania as they are rather connected with Australia, so it is more practical to have a few hours difference than more than 20, which leads to another date in most cases.&lt;/p&gt;

&lt;p&gt;Let’s consider a flight connection finder. In the case of round-trip flights, the users can choose both departure and return dates/times. It may be obvious that the return time must be after the departure.&lt;/p&gt;

&lt;p&gt;Of course, this couldn’t be further from the truth!&lt;/p&gt;

&lt;p&gt;Keep in mind that those times are in local time zones.&lt;/p&gt;

&lt;p&gt;Take a look &lt;a href="https://travel.stackexchange.com/questions/74308/return-date-earlier-than-departure-date-on-the-same-flight-booking" rel="noopener noreferrer"&gt;at this example&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Departure from Tokyo at 00:30 (offset +09:00) to San Francisco (offset -07:00)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Arrival in San Francisco at 18:00, the previous day in local time!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return from San Francisco at 19:50 (still previous when relative to initial departure)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, you can go somewhere and return yesterday. See this example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-dppbk.nitrocdn.com%2FyQqYxSaTIazRSTSDbfxPrqFzJPJOhsTG%2Fassets%2Fimages%2Foptimized%2Frev-3566a21%2Fwww.thedroidsonroids.com%2Fwp-content%2Fuploads%2F2021%2F04%2FEdge-Cases-graphics-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-dppbk.nitrocdn.com%2FyQqYxSaTIazRSTSDbfxPrqFzJPJOhsTG%2Fassets%2Fimages%2Foptimized%2Frev-3566a21%2Fwww.thedroidsonroids.com%2Fwp-content%2Fuploads%2F2021%2F04%2FEdge-Cases-graphics-2.png" alt="Airplane timetable with returning yesterday" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Time zone naming
&lt;/h2&gt;

&lt;p&gt;Another potential edge case in app/backend development you may face is connected to time zone naming.&lt;/p&gt;

&lt;p&gt;You might notice that we can call a timezone by its offset eg. +01:00 or by its identifier eg. Europe/Berlin. Note that the latter notation gives multiple benefits: it carries information about DST transitions (Berlin has an offset +02:00 in the summer) and it also holds historical data.&lt;/p&gt;

&lt;p&gt;For example, both the Europe/Warsaw and Europe/Berlin zones seem to be identical nowadays. They both have equal offsets all year, with DST transitions also always occurring at the same moments.&lt;/p&gt;

&lt;p&gt;However, it was not always the case in the past! For example, in 1977 there was no DST in Berlin at all but Warsaw observed it from April 3th to September 25th (completely different from what we have today).&lt;/p&gt;

&lt;p&gt;Note that timezone identifiers are &lt;a href="https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones" rel="noopener noreferrer"&gt;built upon city names&lt;/a&gt;. This is intentional due to the fact that country boundaries are subject to change much more often than city names.&lt;/p&gt;

&lt;p&gt;For example, the Crimea has in fact changed from Ukraine to Russia but the city names stay unchanged. The Europe/Simferopol zone can be used no matter if we need current or historical data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day vs nychthemeron, period vs duration
&lt;/h2&gt;

&lt;p&gt;Let’s say that something lasts one day. So, if it starts at 09:15:00 AM then we can add 24 hours and to get the ending time (09:15:00 AM next day exclusively, in this case).&lt;/p&gt;

&lt;p&gt;Well, it is not always so easy! Keep in mind that we can have a DST transition when the clock is artificially adjusted forward or backwards. This means the day usually lasts 24 hours but, occasionally, it may be 23, 25 or even more strange values like 27 or 23 and a half hours (do you remember Casey and Lord Howe?).&lt;/p&gt;

&lt;p&gt;It is important to not blindly treat days as 24 hours when implementing business logic. You should use the appropriate date/time APIs for that. For example, in Java, we have distinct classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/Period.html" rel="noopener noreferrer"&gt;Period&lt;/a&gt;, which represents calendar days — perfect for things like subscription validity which is measured in days, months or years&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html" rel="noopener noreferrer"&gt;Duration&lt;/a&gt;, which represents continuous time eg. 24 hours, no matter what the date in the calendar is. It’s perfect for measuring spans of actions, such as travel or the time since device/program start.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some languages have distinct words for sunlit states (in English — day) and consecutive 24 hours (in English — nychthemeron, but it’s not commonly used).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Date/time representation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nmrqh3k0640ir8dt5pf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nmrqh3k0640ir8dt5pf.png" alt="Date/Time notation infographic" width="461" height="537"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://xkcd.com/1179/" rel="noopener noreferrer"&gt;xkcd.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When communicating with other systems, such as via REST API, it is important to agree about data formats. If you see a date displayed as 10/12/2021, it may be in either the US format (October 12th) or UK format (December 10th).&lt;/p&gt;

&lt;p&gt;It is better to use some unambiguous representation!&lt;/p&gt;

&lt;p&gt;For date and time, we have the &lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;ISO 8601 standard&lt;/a&gt;. Note that not only absolute date/times are standardized, but also periods. It is also important to use the proper type — local (for representing data like alarm clock triggering times or dates of birth) or zoned (for representing moments in time, like event starts or taking pictures).&lt;/p&gt;

&lt;p&gt;Custom, non-standardized formats usually lead to confusion and bugs, so avoid them.&lt;/p&gt;

&lt;p&gt;When displaying the date and time in the UI, you have to take the user’s locale into account. The displayed format depends on the language and region. Look at the following examples which all refer to the same date:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;4/18/21 — US English&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;18/04/2021 — UK English&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;18.04.2021 — Romanian&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;١٨‏/٤‏/٢٠٢١ — Saudi Arabia, Arabic with Eastern Arabic numerals&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are predefined format types provided by date/time libraries, such as &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/FormatStyle.html" rel="noopener noreferrer"&gt;FormatStyle&lt;/a&gt; in &lt;code&gt;java.time&lt;/code&gt;. Use them if possible. Otherwise, you can construct more customized formats using standardized pattern symbols. See the &lt;a href="http://cldr.unicode.org/translation/date-time-1/date-time-patterns" rel="noopener noreferrer"&gt;CLDR documentation&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;It is worth noting that, in the case of months, quarters, and days of weeks, there are also standalone variants. They are meaningful only in some languages (not in English). In Polish, for example, April is called &lt;em&gt;kwiecień&lt;/em&gt; — this is a standalone version. However, if it connected with a day (eg. April 18th) the text becomes &lt;em&gt;18 kwietnia&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Dealing with dates and time is not straightforward. However, if you follow the standards described above and use proper types, you can avoid huge mistakes.&lt;/p&gt;

&lt;p&gt;I hope my insight on dates and time edge cases will be useful in your projects. Good luck!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.thedroidsonroids.com/blog/edge-cases-in-app-and-backend-development-dates-and-time" rel="noopener noreferrer"&gt;https://www.thedroidsonroids.com&lt;/a&gt; on April 26, 2021.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>java</category>
      <category>trivia</category>
    </item>
    <item>
      <title>How to Duplicate a Git Repository? Step-by-step Guide</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Mon, 05 Aug 2024 16:55:20 +0000</pubDate>
      <link>https://dev.to/koral/how-to-duplicate-a-git-repository-step-by-step-guide-2eeh</link>
      <guid>https://dev.to/koral/how-to-duplicate-a-git-repository-step-by-step-guide-2eeh</guid>
      <description>&lt;p&gt;Learn how to mirror a git repository in a few simple steps&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Before I describe how to duplicate a git repository, let me explain you the context. At Droids On Roids, we prefer using our own tools for CI/CD, VCS hosting and other processes. All our developers know them well, which speeds up development in comparison to using separate clients’ tools for each individual project.&lt;/p&gt;

&lt;p&gt;Usually, it doesn’t matter what we are using internally. However, regarding the repository, clients may want to store the code on servers controlled by them. It’s perfectly understandable, since they usually also purchase the intellectual property, so they own the entire code.&lt;/p&gt;

&lt;p&gt;Sometimes that hosting on the client side may be not perfect.&lt;/p&gt;

&lt;p&gt;For example, it may use a different provider eg. GitLab instead of GitHub, which we use on a daily basis. Or it may even be a self-hosted Git server without any web interface accessible using only a single key.&lt;/p&gt;

&lt;p&gt;How can we perform a code review process or setup webhooks for CI on something like that? Let’s see what we can do!&lt;/p&gt;

&lt;h2&gt;
  
  
  The cases
&lt;/h2&gt;

&lt;p&gt;Firstly, the necessity to store the code in a particular place does not mean that it is the only location.&lt;/p&gt;

&lt;p&gt;We can still use our favorite GitHub for daily development and only duplicate (mirror) the code to the client’s repository.&lt;/p&gt;

&lt;p&gt;To do that efficiently, let’s first answer a few questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we need to mirror everything, or only the particular branches and/or tags?&lt;/li&gt;
&lt;li&gt;What is the trigger for the mirror operation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usually, the client is only interested in some milestones/releases, rather than all the intermediate work. In such a case, we usually want to mirror only the main branch and maybe some tags.&lt;/p&gt;

&lt;p&gt;Note that we are not taking any legal issues into account here. Check if there are no restrictions regarding the storage location in the contract. Sometimes it may be forbidden to push the code to public clouds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scope
&lt;/h2&gt;

&lt;p&gt;Mirroring everything is possible but keep in mind that the target hosting configuration has to be compatible with the source.&lt;/p&gt;

&lt;p&gt;For example, branches containing force pushed commits on the source must not be protected on the target. Pushing some constructs not used on the target side may also produce errors or warnings eg. Gerrit creates references (which are not necessarily pointing to branches or tags) which may not be understood by the likes of GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trigger
&lt;/h2&gt;

&lt;p&gt;Mirroring is often triggered when some milestone is reached, such as at the end of the iteration/sprint or just on the regular basis eg. weekly or daily. I would recommend doing that operation on a separate CI build, not the one which you even can have by occasion.&lt;/p&gt;

&lt;p&gt;For example, even if you have a “sprint” build releasing the product, you should not add mirroring as one of its stages/steps. Why?&lt;/p&gt;

&lt;p&gt;Well, you have no control over the target repository. If a repo becomes unreachable, someone revokes the key or pushes the same tag you want to push, then your mirroring will fail, even if the artifact was released successfully.&lt;/p&gt;

&lt;p&gt;Moreover, the local copy used to build the product may not checkout a branch you want to mirror, but instead checkout a particular commit as a &lt;a href="https://git-scm.com/docs/git-checkout#_detached_head" rel="noopener noreferrer"&gt;detached HEAD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, on Bitrise CI a trigger associated with a push (to branch) action checks out the pushed commit. It was on the top of the branch at the point of triggering but at the time of building (cloning), new commits may appear after it, because there might be pushes in the meantime.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Let’s start coding! Mirroring itself consist of 2 operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloning (or preparing an already cloned copy).&lt;/li&gt;
&lt;li&gt;Pushing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting access to the repositories by activating SSH keys, for example, is out of the scope but, if needed, it has to be performed before accessing the particular repository.&lt;/p&gt;

&lt;p&gt;In case of the (recommended) fresh build, cloning looks like this for a single branch:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone --bare --single-branch --no-tags --branch=&amp;lt;branch name&amp;gt; &amp;lt;source url&amp;gt;&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Or like this for the entire repository:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone --mirror &amp;lt;source url&amp;gt;&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;&amp;lt;branch name&amp;gt;&lt;/code&gt; is the name of the branch you want to clone, and &lt;code&gt;&amp;lt;source url&amp;gt;&lt;/code&gt; is the source (not the target!) repo URL. Note the trailing dot! It means the current directory.&lt;/p&gt;

&lt;p&gt;Make sure that it is empty. Some CI platforms may create some temporary files there or set the initial directory to something usually not empty, such as &lt;a href="https://en.wikipedia.org/wiki/Home_directory" rel="noopener noreferrer"&gt;$HOME&lt;/a&gt;. In such cases, execute this command beforehand: &lt;code&gt;pushd $(mktemp -d)&lt;/code&gt;.It will create a temporary folder (with a unique, not occupied name) and change the current directory to it (like cd command).&lt;/p&gt;

&lt;p&gt;Note also the &lt;code&gt;--bare&lt;/code&gt; and &lt;code&gt;--mirror&lt;/code&gt; options. They ensure no working directory is created. As a result, no head is checked out (it is useless when we don’t need to work directly on the repository content). That’s why the bare clone operation is faster than the normal clone. If you want to also push the tags, just remove the &lt;code&gt;--no-tags&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;In order to send the changes to the target repository, you can use the git push command. In the case of a single branch, omit the –tags option if you don’t need to push the tags:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git push --tags &amp;lt;target url&amp;gt; &amp;lt;branch name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or for &lt;a href="https://git-scm.com/docs/git-push#Documentation/git-push.txt---mirror" rel="noopener noreferrer"&gt;mirroring&lt;/a&gt; the entire repo:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git push --mirror &amp;lt;target url&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Be careful that such a push will also (try to) replicate all forced pushes and branch deletions! Some data may be practically lost after that operation. Restrictions enforced by hostings like protected branches or the inability to delete the default branch will still be held.&lt;/p&gt;

&lt;p&gt;Make sure that the user on behalf whom the push is performed has the appropriate permissions. They must be able to write to the repo but should not have admin privileges!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Requirement to store the code in your client’s repository does not always mean you have to use their repository. You can simply synchronize your internal repo with your client’s one, using a few lines of the shell script.&lt;/p&gt;

&lt;p&gt;Keep in mind that the push may fail (which should not affect the rest of your workflow) or irreversibly overwrite the data. I hope thanks to this article you will know how to duplicate a git repository. Good luck!&lt;/p&gt;

&lt;p&gt;The post appeared first on &lt;a href="https://www.thedroidsonroids.com/blog/how-to-duplicate-a-git-repository" rel="noopener noreferrer"&gt;Droids On Roids&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>git</category>
      <category>mobile</category>
      <category>vcs</category>
    </item>
    <item>
      <title>What does GDPR mean for Mobile App Owners? – 12 Use Cases</title>
      <dc:creator>Karol Wrótniak</dc:creator>
      <pubDate>Mon, 05 Aug 2024 11:53:11 +0000</pubDate>
      <link>https://dev.to/koral/what-does-gdpr-mean-for-mobile-app-owners-12-use-cases-441i</link>
      <guid>https://dev.to/koral/what-does-gdpr-mean-for-mobile-app-owners-12-use-cases-441i</guid>
      <description>&lt;p&gt;In this article, you will find 12 useful GDPR Use Cases for App Owners, Product Owners and everyone who wants to develop a mobile app. You will also read about the basics of GDPR  – What it is, Who it’s directed to, How high penalties are and – the most important question – What does it mean for App Owners?  Let’s check if your app is GDPR-compliant!&lt;/p&gt;

&lt;p&gt;Article co-authored by &lt;strong&gt;Krzysztof Werner&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  INTRODUCTION – BASICS ABOUT GDPR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is GDPR?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679&amp;amp;from=EN" rel="noopener noreferrer"&gt;General Data Protection Regulation&lt;/a&gt; (GDPR) (EU) 2016/679 is a regulation in EU law on data protection and privacy. Its aim is to give explicit control over personal data to its subjects. The Regulation was adopted on April 27th, 2016 and it becomes enforceable from May 25th, 2018, after a two-year transition period.&lt;/p&gt;

&lt;p&gt;So, May 25th, 2018 is a red-letter day for many companies. It’s a major change regarding data privacy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhnsbjqcgnr68l9ddh4k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxhnsbjqcgnr68l9ddh4k.gif" alt="GDPR meaning for app owners" width="480" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Who is GDPR directed to?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.whitecase.com/publications/article/chapter-4-territorial-application-unlocking-eu-general-data-protection" rel="noopener noreferrer"&gt;Citizens of the entire EU&lt;/a&gt;, as well as Norway, Iceland, and Liechtenstein,&lt;/strong&gt; will become &lt;strong&gt;the subjects of GDPR&lt;/strong&gt; but the &lt;strong&gt;regulations will have a global impact for organizations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are a company owner who processes the personal data of EU citizens or citizens of the aforementioned countries, GDPR applies to you. Even if your company is registered outside of the EU, Norway, Iceland, or Liechtenstein.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means that you need to understand GDPR and start working on a plan to meet its requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller, Processor, Data Subject &amp;amp; Data Protection Officer – definitions
&lt;/h3&gt;

&lt;p&gt;Let’s explain a few definitions used in the GDPR world.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Controller (or administrator)&lt;/strong&gt; – a natural or legal person which uses the data to achieve business goals.
In our case, it is usually the application owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data processor&lt;/strong&gt;  – a natural or legal person which processes the data on behalf of a controller.
For example, 3rd party services like Google, Amazon, Fabric, HockeyApp and so on are all data processors. Sometimes collaborating/&lt;a href="https://www.thedroidsonroids.com/blog/benefits-outsourcing-software-development" rel="noopener noreferrer"&gt;outsourced development&lt;/a&gt; companies may also be considered data processors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data subject&lt;/strong&gt;  – a natural person whose data is processed. Basically, in our case, it is an app user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data protection officer&lt;/strong&gt;  – a natural person designated by the controller or processor to help them and their users with GDPR compliance. This is only required if the amount of processed personal data is significant and/or such data is sensitive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GDPR Penalties
&lt;/h3&gt;

&lt;p&gt;The controller and processor are subject to administrative fines if they infringe the provisions laid out in the GDPR, even if the infringement is not intentional. There are two fine tiers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a) Up to 10,000,000 EUR or up to 2 % of the annual turnover&lt;/strong&gt; of the preceding year (whichever is higher) – for the controller, processor, monitoring body and certification body who infringe their obligations.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;b) Up to 20 million EUR or up to 4% respectively&lt;/strong&gt; – ** ** for a controller who infringes the principles of personal data processing. For example, personal data processing without user consent, data subjects’ rights infringements or &lt;a href="https://www.whitecase.com/publications/article/chapter-13-cross-border-data-transfers-unlocking-eu-general-data-protection" rel="noopener noreferrer"&gt;transferring personal data&lt;/a&gt; to a non GDPR-compliant recipient in a third country. &lt;/p&gt;

&lt;p&gt;You can find more details about these fines in &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679&amp;amp;from=EN#d1e6226-1-1" rel="noopener noreferrer"&gt;&lt;strong&gt;Article 83&lt;/strong&gt;&lt;/a&gt; of the GDPR.&lt;/p&gt;

&lt;h2&gt;
  
  
  LET’S TALK ABOUT YOUR APP
&lt;/h2&gt;

&lt;p&gt;We’re 100% office based team with 7-years’ experience&lt;br&gt;&lt;br&gt;
in mobile &amp;amp; web app development&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thedroidsonroids.com/estimate-project" rel="noopener noreferrer"&gt;Estimate project&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT GDPR MEANS FOR APP OWNERS
&lt;/h2&gt;

&lt;p&gt;Never before have the needs of application users in this area been so strongly and comprehensively protected. This is why &lt;strong&gt;we have to take a fresh look at the way we plan and developing an app in order to fully meet GDPR requirements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Note that the regulation itself does not contain any exact step-by-step guidelines. It only gives us a list of the general rules that we must keep in mind when creating software.&lt;/p&gt;

&lt;p&gt;With this in mind, we can expect a legal approach based on precedents that will occur in the future and which we are not able to predict at this point. &lt;strong&gt;Until the first decisions of the courts appear in this matter, we can only presume in which direction the final interpretation of the regulations EU judges will take.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You must also be aware that the lack of adequate security of personal data may result in the outflow of users from your applications. What’s more, the appropriate protection of this need may be a magnet that will additionally attract users and customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, this means that taking the appropriate preparation to meet GDPR standards becomes an added value for your business and can be reflected in future revenues.&lt;/strong&gt; We hope that our guidelines, which you can read below, will help you to take advantage of these upcoming law changes.&lt;/p&gt;

&lt;p&gt;We’ve analyzed several &lt;strong&gt;common use cases related to personal data processing in mobile app development and have prepared some guidelines for you&lt;/strong&gt;. Let’s check them out!&lt;/p&gt;

&lt;h2&gt;
  
  
  USE CASES
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I only have access to pseudonymous data from my app, for example, installation ID and other metrics on Google Analytics or similar tools.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  What exactly do I have to do to become GDPR compliant?
&lt;/h3&gt;

&lt;p&gt;Personal data are any pieces of information about a natural person or any personally identifiable information about that person which gives you the ability to identify them. You should always keep this in mind when you are planning your app development. &lt;strong&gt;It doesn’t matter whether the information is directly and closely related to a given person but&lt;/strong&gt;  &lt;strong&gt;it’s crucial if they have the ability to identify the person or not&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the de-anonymization requires manpower and resources which are disproportionate to the information obtained, your solutions meet GDPR requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I have an app where users can create content e.g. write comments or chat. They might put some personal data there. Does this matter in terms of GDPR?
&lt;/h3&gt;

&lt;p&gt;Yes, it matters, and this should be taken into account during your app planning &amp;amp; development because, according to GDPR guidelines, each user has the right to request the deletion of personal data that could lead to his or her identification.&lt;/p&gt;

&lt;p&gt;You have no influence whether your app’ users place someone’s else personal data within your application. Subjects whose personal data was published without their permission should have the opportunity to contact your Data Protection Officer (or the controller directly if there is no DPO). In such a case, they can send a request to delete such data from your system and you are obligated to provide them with such an option.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I use Google Analytics, Firebase, Crashlytics or HockeyApp as a 3rd party solution to support my development. Are they GDPR compliant?
&lt;/h3&gt;

&lt;p&gt;You need to be sure that any 3rd parties solutions you use are GDPR compliant. You can &lt;strong&gt;confirm this in their Terms Of Service&lt;/strong&gt;. For example, at the time we’re writing this post, &lt;a href="https://docs.fabric.io/android/fabric/data-privacy.html" rel="noopener noreferrer"&gt;Fabric claims&lt;/a&gt; to be ready for GDPR by May 25th, 2018.&lt;/p&gt;

&lt;p&gt;GDPR resolution guidelines &lt;strong&gt;impose an obligation on us to check whether the services we use have security certificates in line with GDPR assumptions&lt;/strong&gt;. According to regulations, &lt;strong&gt;we are jointly responsible for the leakage of personal data from third parties&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Am I obligated to have a written contract with every 3rd party that processes the data?
&lt;/h3&gt;

&lt;p&gt;A written contract is not obligatory. The Regulations offer a certain amount of freedom here and also introduces a broader concept of “another legal act”.&lt;/p&gt;

&lt;p&gt;What needs to be done to ensure that the contract or another legal act meets the requirements for GDPR? It’s simple. &lt;strong&gt;Check whether the provider whose services you will use has a certificate of compliance with GDPR.&lt;/strong&gt; Certification is voluntary, but if you want to be sure whether the service provider is prepared to meet the requirements of GDPR, check whether it has such a certificate.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Do I need to have a Data Protection Officer on board?
&lt;/h3&gt;

&lt;p&gt;No, you don’t have to. GDPR provides a certain freedom of choice in this regard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Data Protection Officer can be either an employee of the Controller or the Processor, as well as a person from outside the group of employees.&lt;/strong&gt; This gives us some freedoms with our options and the ability to reduce costs.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.thedroidsonroids.com/blog/benefits-outsourcing-software-development" rel="noopener noreferrer"&gt;outsourcing model&lt;/a&gt;, based on a contract for the provision of services, can be adapted to our needs and scale. When it comes to the number of hours of cooperation, they can be significantly limited.&lt;/p&gt;

&lt;p&gt;To sum up, you don’t have to create a DPO position in your company. You can engage a DPO from outside in accordance with your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. My application uses only emails and logins (without a first and last name). Does this count as personal data?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes, they do.&lt;/strong&gt; There is no easy method of mass verification to see if e-mail address do or do not contain personal data. However, &lt;strong&gt;we use nicknames on many portals and it is possible to link them to other data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We must remember that, &lt;strong&gt;whenever we are not sure if the elements of the application can help identify the user, we should assume that this is what can happen&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. In the application, we log in with Facebook, Google etc. The app sends a token to the backend, which automatically reads the user’s ID (or e-mail address), but not the first and last name.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  During the validity period of a token (30 min) I can theoretically use it to manually extract personal data. Does this violate assumptions laid out in the GDPR?
&lt;/h3&gt;

&lt;p&gt;The answer to this question is very difficult. There will be &lt;strong&gt;no clear-cut answer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We must accept, as in the first case, that any piece of information about a user which gives you the ability to identify a natural person may violate GDPR rules. On the other hand, we can also assume that this &lt;strong&gt;data must be obtained with reasonable expenses and costs, which slightly softens the tone of the previous rule&lt;/strong&gt;. At present, we are unable to determine how the authorities will approach this.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. I have a store app where personal data is needed for shipping. What should I include in my terms of service?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Store app terms of service must contain a clause indicating that all information&lt;/strong&gt;  about the subject that the consumer enters in the app (name/surname/phone number/address/e-mail and other), &lt;strong&gt;are protected&lt;/strong&gt;. What’s more, the terms of service should also include a full list of user’s rights, such as the right to access, edit &amp;amp; delete their personal data, for example.  &lt;/p&gt;

&lt;p&gt;Moreover, &lt;strong&gt;you must have the user’s consent to process his or her personal data for the purposes of application functionality&lt;/strong&gt;. So, in every app, there must be a simple way for the user to confirm their acceptance of these regulations. Furthermore, you have to be simple and clear about the regulations the users are accepting.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. I have a bug reporting system in the app (like Crashlytics or HockeyApp) and personal information can be found there. Does it matter?
&lt;/h3&gt;

&lt;p&gt;First of all, &lt;strong&gt;you need to be sure that your bug reporting service provider meets GDPR requirements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What’s more, it is important &lt;strong&gt;what kind of data is included in the reports and who has access to them&lt;/strong&gt;. With this in mind, we will be able to identify several different answers. Let’s consider 3 cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a) The people who view reports already have legitimate access to all the data&lt;/strong&gt; (e.g. the company has its own development team) – bug reporting is irrelevant.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;b) Reported data is anonymous or pseudonymous *&lt;/em&gt;(so much so that, in a reasonable time and with sensible means, they cannot be de-anonymized). This way of reporting bugs is the best way to provide all the necessary data for the QA team and it meets the requirements of GDPR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;c) Reports contain personal data and we &lt;a href="https://www.thedroidsonroids.com/blog/16-reasons-to-outsource-your-mobile-app-development-to-poland" rel="noopener noreferrer"&gt;outsource the development team&lt;/a&gt;.&lt;/strong&gt; In this case, the external company must become an entity that processes personal data, as defined by GDPR, or the reports must be filtered and possibly anonymized before transferring them.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Are my development team and QA team obligated to have any certificates or training on personal data protection?
&lt;/h3&gt;

&lt;p&gt;No, they do not have to. &lt;strong&gt;The only person who is required to have specialist knowledge on this subject is the Data Protection Officer&lt;/strong&gt;. As we mentioned above, he or she doesn’t even have to be a team member.&lt;/p&gt;

&lt;p&gt;Of course, it will be &lt;strong&gt;highly advantageous for your business if your Dev and QA team have some knowledge about&lt;/strong&gt;  &lt;strong&gt;GDPR, as they can deliver compliant products faster and without violating GDPR rulings.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  11. My app processes personal data (not specific data outlined in &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679&amp;amp;from=EN#d1e2051-1-1" rel="noopener noreferrer"&gt;Article 9&lt;/a&gt;) but, on the basis of analytics or crashy reporting, it can be determined that a user is likely to have some dysfunction (e.g. he or she uses large text sizes, so they probably have poor eyesight).
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Does Article 9 (processing of specific categories of personal data) apply to me?
&lt;/h3&gt;

&lt;p&gt;No, it doesn’t. We are not dealing here with data that has been given, but with &lt;strong&gt;the assumption&lt;/strong&gt; that it is so. In order to fully determine this, we would have to use significant work and resources, which means you are not in violation of the GDPR rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. I’ve had a personal data leak in my system. What should I do?
&lt;/h3&gt;

&lt;p&gt;You should report such a leak to the supervisory body  &lt;strong&gt;within 72 hours after having become aware of it,&lt;/strong&gt; unless you can prove that said leak doesn’t violate the rights or freedoms of natural persons. ** ** When the notification to the supervisory authority is not made within 72 hours, it shall be accompanied by reasons for the delay. &lt;/p&gt;

&lt;p&gt;This is one of the most significant changes that GDPR introduces. You can read more about it in &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679&amp;amp;from=EN#d1e3434-1-1" rel="noopener noreferrer"&gt;Article 33&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  LET’S TALK ABOUT YOUR APP
&lt;/h2&gt;

&lt;p&gt;We’re 100% office based team with 7-years’ experience&lt;br&gt;&lt;br&gt;
in mobile &amp;amp; web app development&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thedroidsonroids.com/estimate-project" rel="noopener noreferrer"&gt;Estimate project&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  WRAP UP
&lt;/h2&gt;

&lt;p&gt;GDPR is a major change regarding data privacy. It will have a significant impact on mobile applications owners.&lt;/p&gt;

&lt;p&gt;On one hand, it introduces new, strict rules to abide by and brings changes to the app planning &amp;amp; development process. On the other hand, the appropriate protection of personal data can be a magnet for attracting app users &amp;amp; giving them a greater sense of security. &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;In short, undertaking the appropriate preparation to meet GDPR standards can become an added value for your business. *&lt;/em&gt; We hope that our use cases will help you to take advantage of these upcoming law changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo0wmkykkctkamesootr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo0wmkykkctkamesootr.gif" alt="GDPR meaning app development" width="500" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions, leave a comment &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdiiyfecfuhav9w432a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdiiyfecfuhav9w432a2.png" alt="🙂" width="72" height="72"&gt;&lt;/a&gt; Good luck!&lt;/p&gt;

&lt;p&gt;P.S. Stay tuned for the next article about GDPR which will be dedicated to development teams.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://www.thedroidsonroids.com/blog/gdpr-meaning-mobile-app-owners" rel="noopener noreferrer"&gt;What does GDPR mean for Mobile App Owners? – 12 Use Cases&lt;/a&gt; appeared first on &lt;a href="https://www.thedroidsonroids.com" rel="noopener noreferrer"&gt;Droids On Roids&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gdpr</category>
      <category>privacy</category>
      <category>legal</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
