<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jacob Ras</title>
	<atom:link href="https://www.jacobras.nl/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.jacobras.nl</link>
	<description>About native Android and Kotlin multiplatform development.</description>
	<lastBuildDate>Wed, 05 Mar 2025 18:33:46 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.4.5</generator>
	<item>
		<title>Code snippet: Grouping Dependabot PRs</title>
		<link>https://www.jacobras.nl/2025/03/code-snippet-grouping-dependabot-prs/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Wed, 05 Mar 2025 17:01:03 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1683</guid>

					<description><![CDATA[Here&#8217;s how we group our Dependabot PRs so we only get one PR for all patch updates, one PR for all minor updates and one PR per major update, using the grouping feature that was made generally available a year ago. The comments in the snippet below reflect how we deal with this in our&#8230;&#160;<a href="https://www.jacobras.nl/2025/03/code-snippet-grouping-dependabot-prs/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">Code snippet: Grouping Dependabot PRs</span></a>]]></description>
										<content:encoded><![CDATA[
<p>Here&#8217;s how we group our Dependabot PRs so we only get one PR for all patch updates, one PR for all minor updates and one PR per major update, using the grouping feature that was <a href="https://github.blog/changelog/2024-03-28-dependabot-grouped-security-updates-generally-available/" target="_blank" rel="noopener">made generally available</a> a year ago.</p>



<p>The comments in the snippet below reflect how we deal with this in our organisation:</p>



<pre title="dependabot.yml" class="wp-block-code"><code lang="yaml" class="language-yaml line-numbers">version: 2
updates:
- package-ecosystem: gradle
  directory: "/"
  schedule:
    interval: daily
  registries: "*"
  groups:

    # All patch updates together, may be merged when CI is green.
    patch-updates:
      update-types: [ "patch" ]

    # All minor updates together. Requires green CI + manual check.
    minor-updates:
      update-types: [ "minor" ]

  # Space left: 3 PRs of ungrouped major updates. Requires green CI + manual check.
  open-pull-requests-limit: 5

# These update less often, so we leave them ungrouped.
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: weekly
  open-pull-requests-limit: 3</code></pre>



<p>Documentation: <a href="https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#groups--" target="_blank" rel="noopener">https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#groups&#8211;</a></p>



<p>See also: other ways to group dependency updates: <a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/optimizing-pr-creation-version-updates" target="_blank" rel="noopener">https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/optimizing-pr-creation-version-updates</a></p>



<h2 class="wp-block-heading">Why group them?</h2>



<p>Easier update management. We try to stay up-to-date with our dependencies, but upon introducing this it turned out we&#8217;re 13 patch and 28 minor updates behind. Grouping helps us stay up-to-date with less overhead.</p>



<figure class="wp-block-image size-full"><a href="https://www.jacobras.nl/wp-content/uploads/2025/03/image.png"><img fetchpriority="high" decoding="async" width="671" height="262" src="https://www.jacobras.nl/wp-content/uploads/2025/03/image.png" alt="" class="wp-image-1693" srcset="https://www.jacobras.nl/wp-content/uploads/2025/03/image.png 671w, https://www.jacobras.nl/wp-content/uploads/2025/03/image-300x117.png 300w" sizes="(max-width: 671px) 100vw, 671px" /></a><figcaption class="wp-element-caption">Two major updates in single PRs, one PR with all minor updates and another one with all patch updates.</figcaption></figure>



<h3 class="wp-block-heading">Assuring quality</h3>



<p>As commented in the snippet, we deal with testing as follows:</p>



<ul>
<li>Patch updates group PR: can be merged as soon as PR is green (build + tests succeed);</li>



<li>Minor updates group PR: requires green CI + a manual check on device or emulator;</li>



<li>Major update PRs: these each also require a manual check on a device or emulator, next to green CI.</li>
</ul>



<p>Note that updates that you&#8217;re not ready for yet can still be ignored by including them in the configuration.</p>



<p>Inside each PR the grouped updates are listed, including release notes (if available):</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2025/03/image-1.png"><img decoding="async" width="1024" height="902" src="https://www.jacobras.nl/wp-content/uploads/2025/03/image-1-1024x902.png" alt="" class="wp-image-1707" srcset="https://www.jacobras.nl/wp-content/uploads/2025/03/image-1-1024x902.png 1024w, https://www.jacobras.nl/wp-content/uploads/2025/03/image-1-300x264.png 300w, https://www.jacobras.nl/wp-content/uploads/2025/03/image-1-768x677.png 768w, https://www.jacobras.nl/wp-content/uploads/2025/03/image-1.png 1041w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Talk: &#8220;Expanding your Compose app to desktop/web&#8221;</title>
		<link>https://www.jacobras.nl/2025/02/talk-expanding-your-compose-app-to-desktop-web/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Wed, 26 Feb 2025 10:15:00 +0000</pubDate>
				<category><![CDATA[Talks]]></category>
		<category><![CDATA[Compose]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1668</guid>

					<description><![CDATA[February, 2025, at Ahold Delhaize for DAUG (Dutch Android User Group). Link to the presentation on Google Slides: https://docs.google.com/presentation/d/1PAQgNRK6bRjNw6BaqVwGlwMA5LwmqQWSccLyJ6yG5sw/edit?usp=sharing.]]></description>
										<content:encoded><![CDATA[
<p>February, 2025, at Ahold Delhaize for DAUG (Dutch Android User Group).</p>



<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vTCFnnzimWD0w-bAOwn196Gu2AFbRJ9Yr_izuDmKavENPtyM6EeC7VKLB3aA7kVaMLPWQI4QDmikCfq/embed?start=false&#038;loop=false&#038;delayms=3000" frameborder="0" width="100%" height="500" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>



<p>Link to the presentation on Google Slides: <a href="https://docs.google.com/presentation/d/1PAQgNRK6bRjNw6BaqVwGlwMA5LwmqQWSccLyJ6yG5sw/edit?usp=sharing" target="_blank" rel="noopener">https://docs.google.com/presentation/d/1PAQgNRK6bRjNw6BaqVwGlwMA5LwmqQWSccLyJ6yG5sw/edit?usp=sharing</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Apps using Compose Multiplatform on iOS (now beta!) in 2024 — developer insights</title>
		<link>https://www.jacobras.nl/2024/05/compose-multiplatform-ios-beta-in-2024-developer-insights/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Thu, 23 May 2024 09:42:07 +0000</pubDate>
				<category><![CDATA[Kotlin Multiplatform]]></category>
		<category><![CDATA[Compose]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1429</guid>

					<description><![CDATA[You can reply to this article at https://medium.com/@jacobras/apps-using-compose-multiplatform-on-ios-now-beta-in-2024-developer-insights-fe24b224d754. A year ago I wrote Popular apps using Kotlin Multiplatform (KMP) in 2023 — and what you can learn from them. More apps have joined since then (like Forbes and Bolt), but in this post I&#8217;m focusing on one step further: user interface. Compose Multiplatform for iOS&#8230;&#160;<a href="https://www.jacobras.nl/2024/05/compose-multiplatform-ios-beta-in-2024-developer-insights/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">Apps using Compose Multiplatform on iOS (now beta!) in 2024 — developer insights</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/apps-using-compose-multiplatform-on-ios-now-beta-in-2024-developer-insights-fe24b224d754" target="_blank" rel="noopener">https://medium.com/@jacobras/apps-using-compose-multiplatform-on-ios-now-beta-in-2024-developer-insights-fe24b224d754</a>.</p>



<p>A year ago I wrote <a href="https://www.jacobras.nl/2023/07/popular-apps-kotlin-multiplatform-kmp-2023/" data-type="post" data-id="156">Popular apps using Kotlin Multiplatform (KMP) in 2023 — and what you can learn from them</a>. More apps have joined since then (like Forbes and Bolt), but in this post I&#8217;m focusing on one step further: user interface. Compose Multiplatform for iOS has been <strong>officially promoted to beta today</strong>, but companies have already successfully been using it in production for a while! Let&#8217;s take a look at a couple of these apps. I asked several developers to describe their experience with using Compose Multiplatform (informally abbreviated &#8220;CMP&#8221;) on iOS, and here&#8217;s what they said.</p>



<p>Links are included to both the Android and iOS versions of publicly available apps, so you can give them a try for yourself (and maybe see if can spot some Material ripples <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />).</p>



<figure class="wp-block-image size-full"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-17.png"><img loading="lazy" decoding="async" width="1000" height="500" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-17.png" alt="" class="wp-image-1572" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-17.png 1000w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-17-300x150.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-17-768x384.png 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></a></figure>



<p class="article_info_box">To give an indication of the size/popularity, I include <strong>download numbers from Android</strong>, as Apple doesn&#8217;t publish those numbers. Screenshots are marketing pictures provided by the developer.</p>



<h2 class="wp-block-heading">Instabox (internal apps)</h2>



<p>The developers at Swedish logistics company Instabox started building their internal iOS apps (used by many thousands of users) with SwiftUI, but after seeing a Compose demo running on iOS they built a prototype, showed it to their CTO, who liked it, and then continued working on it. They were able to build the prototype in only a couple of weeks, because the app was already structured for and with Kotlin Multiplatform.</p>



<p>In <a href="https://www.youtube.com/watch?v=YsQ-2lQYQ8M" target="_blank" rel="noopener"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></a> <a href="https://www.youtube.com/watch?v=YsQ-2lQYQ8M" target="_blank" rel="noopener">an episode of the <em>Talking Kotlin</em> podcast</a> (from August 2023), the developers elaborated their choice and process of working with CMP. They&#8217;re particularly happy about how native APIs are simple to use with KMP and how &#8220;just being able to move files to [common]&#8221; is a strong point of Kotlin Multiplatform.</p>



<blockquote class="wp-block-quote">
<p>&#8220;It&#8217;s fantastic just to keep your expertise in Kotlin and to be able to make this nice app still. I feel it&#8217;s cross-platform done right.&#8221;</p>
<cite>Instabee developer Johannes Svensson</cite></blockquote>



<p>At KotlinConf 2024 a talk called &#8220;Compose Multiplatform on mobile at Instabee for over a year&#8221; will be given. I will update this article once a recording is available.</p>



<p>When asked what the biggest thing was they had to rewrite themselves, they mentioned navigation. At the time it wasn&#8217;t multiplatform yet, but lead developer Johannes told me they&#8217;ll most probably adopt the now-multiplatform Compose Navigation in the near future. Currently, the apps are using Voyager for navigation with some custom code on op of it (for back gesture handling, for example).</p>



<h2 class="wp-block-heading">Markaz (1M+)</h2>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-8.png"><img loading="lazy" decoding="async" width="1024" height="527" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-8-1024x527.png" alt="" class="wp-image-1544" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-8-1024x527.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-8-300x155.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-8-768x396.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-8-1536x791.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-8.png 1988w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>Users can buy and sell products online through this app. Everything from business logic to UI is shared Kotlin code. It&#8217;s aimed at the Pakistan market, so it may not be available in all countries. In a recent update, iOS&#8217; back swipe gesture was added, on top of the basic iOS look &amp; feel offered by Compose Multiplatorm out of the box, like native scrolling.</p>



<blockquote class="wp-block-quote">
<p>&#8220;We started working with KMP back in 2021. We had only ten days to build an app for both Android and iOS. We thought about Flutter, but we needed camera features, QR scanning et cetera. So, Kotlin Multiplatform made more sense. We went with a native UI + KMP for sharing business logic and we were able to complete the app in only seven days. Our main product was pure native at that time but we revamped it in November 2022 and shared the business logic with KMP and UI in Jetpack Compose.</p>



<p>We did not have ios app at that time. The motivation was that whenever we needed an iOS app, we would have the business logic ready and we would only need to build the native UI layer. In November 2023, the number of customers asking for an iOS app shot up and we decided what to do. Again, working in a startup we had tight deadlines. Compose was pre-alpha, but we decided to go with it. We ported our Jetpack Compose code to Compose Multiplatform. Dialogs, flow rows and such were not yet available, so we wrote custom components. We were using voyager and wrote custom code on top of it for our needs. </p>



<p>We ported the app to iOS in only 28 days with camera experience, payment integrations and much more. When we launched, there were memory leaks initially, but it&#8217;s very smooth now. The team had zero experience on UIkit prior to this, but writing UIkit in kotlin really helped us in writing custom components.&#8221;</p>
<cite>Markaz developer Kashif Mehmood</cite></blockquote>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-1 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/05/Markaz.png" alt="" class="wp-image-1491 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/Markaz.png 246w, https://www.jacobras.nl/wp-content/uploads/2024/05/Markaz-150x150.png 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>Markaz</strong><br>App Store: <a href="https://apps.apple.com/se/app/instabox/id1562697442?l=en-GB" target="_blank" rel="noopener"></a><a href="https://apps.apple.com/pk/app/markaz-resell-and-earn-money/id6470020517" target="_blank" rel="noopener">https://apps.apple.com/pk/app/markaz-resell-and-earn-money/id6470020517</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=com.markaz.app" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=com.markaz.app</a></p>
</div></div>
</div>
</div>



<h2 class="wp-block-heading">Wrike (1M+)</h2>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-10.png"><img loading="lazy" decoding="async" width="1024" height="529" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-10-1024x529.png" alt="" class="wp-image-1547" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-10-1024x529.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-10-300x155.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-10-768x396.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-10-1536x793.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-10.png 1984w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>Unveiled at KotlinConf today: Wrike is using Compose on iOS in production, for their calendar part of the app. The lead developer said the following during the keynote video (link at the end of this post):</p>



<blockquote class="wp-block-quote">
<p>&#8220;We&#8217;ve decided to go ahead with Compose Multiplatform for building the calendar view. In less than 3 months, it was live on the App Store and Google Play. And the best part: this efficiency came without the need to learn a new language or hire a new development team, by just leveraging our existing knowledge of Kotlin.&#8221;</p>
<cite>Wrike technical lead Alex Askerov</cite></blockquote>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-2 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/05/wrike.webp" alt="" class="wp-image-1506 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/wrike.webp 246w, https://www.jacobras.nl/wp-content/uploads/2024/05/wrike-150x150.webp 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>Wrike</strong><br>App Store: <a href="https://apps.apple.com/ms/app/wrike-work-as-one/id890048871" target="_blank" rel="noopener">https://apps.apple.com/ms/app/wrike-work-as-one/id890048871</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=com.wrike" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=com.wrike</a></p>
</div></div>
</div>
</div>



<h2 class="wp-block-heading">Campus (100K+)</h2>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-19.png"><img loading="lazy" decoding="async" width="1024" height="576" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-19-1024x576.png" alt="" class="wp-image-1581" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-19-1024x576.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-19-300x169.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-19-768x432.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-19-1536x864.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-19.png 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>Campus &#8211; Schedule of classes (original name: Кампус &#8211; Расписание занятий) is an app for university students to keep track of their lesson schedule. The app is mostly native, but uses Compose Multiplatform for the &#8220;courses&#8221; section (the second tab in the app). The developer integrated CMP in the middle of 2023, shortly after it reached alpha state.</p>



<blockquote class="wp-block-quote">
<p>&#8220;We wanted to test a hypothesis with new content for our users. It was supposed to be a new standalone section, practically unrelated to the rest of the application. Since it was just a hypothesis and we were unsure if we would keep it in the app at all, we needed a solution with minimal effort. However, in case the experiment was successful, we wanted the ability to develop this section further and integrate it more tightly with the application.</p>



<p>At that time, the project already used shared Kotlin Multiplatform logic, but the entire UI was native. For the new section, we considered either Compose Multiplatform or WebView. Both solutions allowed us to conduct the experiment quickly. But with WebView, if the experiment succeeded, we would have to discard the work done and re-implement it using native tools (and shared Kotlin logic), whereas with Compose we immediately get ready-made shared logic, a ready-made Android UI, and if desired, we could replace the UI on iOS with a native version. Therefore, we chose Compose Multiplatform.</p>



<p>Subsequently, the section remained on Compose Multiplatform (and is still available). However, we integrate Redwood by CashApp too &#8211; the main screen of the application is made using it. With Redwood, we can get a native UI from the shared Compose code, without drawing with Skia, providing users with a 100% native look and feel. Compose Multiplatform on iOS still has drawbacks compared to the native UI, but it allows for very fast deployment on both platforms, and in the future, if desired, we can replace the iOS UI with a native one while keeping the shared logic.&#8221;</p>
<cite>IceRock (Campus developer) CTO Aleksey Mikhailov</cite></blockquote>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-3 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/05/campus.webp" alt="" class="wp-image-1502 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/campus.webp 246w, https://www.jacobras.nl/wp-content/uploads/2024/05/campus-150x150.webp 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>Кампус &#8211; Расписание занятий </strong>(<strong>Campus</strong>)<br>App Store: <a href="https://apps.apple.com/ru/app/кампус-расписание-занятий/id1534975833" target="_blank" rel="noopener">https://apps.apple.com/ru/app/кампус-расписание-занятий/id1534975833</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=ru.dewish.campus" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=ru.dewish.campus</a></p>
</div></div>
</div>
</div>



<h2 class="wp-block-heading">Ashampoo Photo Organizer (upcoming)</h2>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-12.png"><img loading="lazy" decoding="async" width="1024" height="412" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-12-1024x412.png" alt="" class="wp-image-1549" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-12-1024x412.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-12-300x121.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-12-768x309.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-12-1536x618.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-12.png 1885w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>The first version of Ashampoo Photos for iOS was written in SwiftUI. As soon as Compose Multiplatform became available, the developer switched development. The new version built with Compose UI is being tested and not yet available at the time of writing this post. However, with Compose Multiplatform running on more than &#8220;just&#8221; mobile platforms, the software is also available for Windows (and soon MacOS) at <a href="https://www.ashampoo.com/photo-organizer" target="_blank" rel="noopener">https://www.ashampoo.com/photo-organizer</a>.</p>



<blockquote class="wp-block-quote">
<p>&#8220;The first version of Ashampoo Photos iOS was written in SwiftUI until Compose for iOS was available. I dropped that SwiftUI version, because it was tiresome to do double work and also it had not all the features because (to my surprise) SwiftUI APIs are lacking. There are a lot of things you just can’t do with SwiftUI &#8211; for example handling scrollstates of a list.&#8221;</p>
<cite>Ashampoo developer Stefan Oltmann</cite></blockquote>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-4 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="480" height="480" src="https://www.jacobras.nl/wp-content/uploads/2024/05/ashampoo.png" alt="" class="wp-image-1498 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/ashampoo.png 480w, https://www.jacobras.nl/wp-content/uploads/2024/05/ashampoo-300x300.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/ashampoo-150x150.png 150w" sizes="(max-width: 480px) 100vw, 480px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>Ashampoo Photo Organizer</strong><br>App Store: <em>coming soon</em><br>Play Store: <a href="https://play.google.com/store/apps/details?id=com.ashampoo.photos" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=com.ashampoo.photos</a></p>
</div></div>
</div>
</div>



<h2 class="wp-block-heading">Open source: KotlinConf (5K+), Twine RSS Reader (10K+) &amp; FindTravelNow</h2>



<p>The last three apps in this list are open source! Don&#8217;t skip checking these out if you&#8217;re curious to see how exactly they&#8217;re built. First off: the official KotlinConf app!</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-16.png"><img loading="lazy" decoding="async" width="1024" height="529" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-16-1024x529.png" alt="" class="wp-image-1565" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-16-1024x529.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-16-300x155.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-16-768x397.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-16-1536x794.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-16.png 1991w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-5 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/06/kotlinconf.webp" alt="" class="wp-image-1566 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/06/kotlinconf.webp 246w, https://www.jacobras.nl/wp-content/uploads/2024/06/kotlinconf-150x150.webp 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>KotlinConf</strong><br>App Store: <a href="https://apps.apple.com/us/app/kotlinconf/id1299196584" target="_blank" rel="noopener">https://apps.apple.com/us/app/kotlinconf/id1299196584</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=com.jetbrains.kotlinconf" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=com.jetbrains.kotlinconf</a><br>Source code: <a href="https://github.com/JetBrains/kotlinconf-app" target="_blank" rel="noopener">https://github.com/JetBrains/kotlinconf-app</a></p>
</div></div>
</div>
</div>



<p>Then Twine, which is an RSS reader with 10K+ downloads:</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-13.png"><img loading="lazy" decoding="async" width="1024" height="531" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-13-1024x531.png" alt="" class="wp-image-1550" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-13-1024x531.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-13-300x156.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-13-768x398.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-13-1536x797.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-13.png 1992w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-6 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/05/twine-rss-reader.webp" alt="" class="wp-image-1513 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/twine-rss-reader.webp 246w, https://www.jacobras.nl/wp-content/uploads/2024/05/twine-rss-reader-150x150.webp 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>Twine &#8211; RSS Reader</strong><br>App Store: <a href="https://apps.apple.com/us/app/twine-rss-reader/id6465694958" target="_blank" rel="noopener">https://apps.apple.com/us/app/twine-rss-reader/id6465694958</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=dev.sasikanth.rss.reader" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=dev.sasikanth.rss.reader</a><br>Source code: <a href="https://github.com/msasikanth/twine" target="_blank" rel="noopener">https://github.com/msasikanth/twine</a></p>
</div></div>
</div>
</div>



<p>And finally FindTravelNow is a &#8220;metasearch travel application&#8221; for finding cheap airplane tickets around the world:</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/05/image-14.png"><img loading="lazy" decoding="async" width="1024" height="528" src="https://www.jacobras.nl/wp-content/uploads/2024/05/image-14-1024x528.png" alt="" class="wp-image-1554" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/image-14-1024x528.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-14-300x155.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-14-768x396.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-14-1536x792.png 1536w, https://www.jacobras.nl/wp-content/uploads/2024/05/image-14.png 1987w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<div class="wp-block-columns app-box is-layout-flex wp-container-core-columns-layout-7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<div class="wp-block-media-text is-vertically-aligned-center" style="grid-template-columns:15% auto"><figure class="wp-block-media-text__media"><img loading="lazy" decoding="async" width="246" height="246" src="https://www.jacobras.nl/wp-content/uploads/2024/05/FindTravelNow.webp" alt="" class="wp-image-1556 size-full" srcset="https://www.jacobras.nl/wp-content/uploads/2024/05/FindTravelNow.webp 246w, https://www.jacobras.nl/wp-content/uploads/2024/05/FindTravelNow-150x150.webp 150w" sizes="(max-width: 246px) 100vw, 246px" /></figure><div class="wp-block-media-text__content">
<p class="has-text-align-left"><strong>FindTravelNow</strong><br>App Store: <a href="https://apps.apple.com/gr/app/findtravelnow/id6471192930" target="_blank" rel="noopener">https://apps.apple.com/gr/app/findtravelnow/id6471192930</a><br>Play Store: <a href="https://play.google.com/store/apps/details?id=com.travelapp.findtravelnow" target="_blank" rel="noopener">https://play.google.com/store/apps/details?id=com.travelapp.findtravelnow</a><br>Source code: <a href="https://github.com/mirzemehdi/FindTravelNow-KMM" target="_blank" rel="noopener">https://github.com/mirzemehdi/FindTravelNow-KMM</a></p>
</div></div>
</div>
</div>



<h2 class="wp-block-heading">Closing thoughts</h2>



<p>I hope this round-up inspires those wanting to move into cross-platform development. Remember that, like with Kotlin Multiplatform in general, if you try any of it but decide to not move further with the multiplatform part, you&#8217;re always left with a functioning and modern Android app.</p>



<p>Check out the JetBrains site for more information on how to get started: <a href="https://www.jetbrains.com/lp/compose-multiplatform/" target="_blank" rel="noopener">https://www.jetbrains.com/lp/compose-multiplatform/</a> and today&#8217;s <a href="https://www.youtube.com/watch?v=Ar73Axsz2YA" target="_blank" rel="noopener">KotlinConf keynote</a> for the Compose iOS beta announcement.</p>



<p>Special thanks to the developers mentioned above who provided valuable input for this article and the community <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Talk: “Your app can be multiplatform, too”</title>
		<link>https://www.jacobras.nl/2024/04/talk-your-app-can-be-multiplatform-too/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Thu, 25 Apr 2024 11:00:00 +0000</pubDate>
				<category><![CDATA[Talks]]></category>
		<category><![CDATA[Compose]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1628</guid>

					<description><![CDATA[Flexibility is one of the key characteristics of Kotlin Multiplatform. It means you can use it where needed, but also that shared code can live next to platform-specific code in the same codebase. Let’s see how that works in practice by taking a well-known app and gradually adopting/migrating it to Kotlin &#038; Compose Multiplatform.]]></description>
										<content:encoded><![CDATA[
<p>April, 2024, at Q42 for DAUG (Dutch Android User Group).</p>



<p>Flexibility is one of the key characteristics of Kotlin Multiplatform. It means you can use it where needed, but also that shared code can live next to platform-specific code in the same codebase. Let’s see how that works in practice by taking a well-known app and gradually adopting/migrating it to Kotlin &amp; Compose Multiplatform.</p>



<p>Slides:</p>



<iframe loading="lazy" src="https://docs.google.com/presentation/d/e/2PACX-1vRgcVy2lleqcHIHVW3oJd0AP2zo2A1TRAKSuuzAbH_X2DTYfdMcqnlhCZ8NIGb7TB2WPW3agabZcTHi/embed?start=false&#038;loop=false&#038;delayms=3000" frameborder="0" width="100%" height="500" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Migrating to Koin Annotations in a multiplatform project</title>
		<link>https://www.jacobras.nl/2024/02/migrating-to-koin-annotations-in-a-multiplatform-project/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Tue, 27 Feb 2024 14:33:44 +0000</pubDate>
				<category><![CDATA[Kotlin Multiplatform]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Kotlin]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1284</guid>

					<description><![CDATA[Koin is my favourite DI library, but out of the box it&#8217;s a bit verbose. With Koin Annotations, the overhead of manually writing module{} declarations is removed, making it as easy to use as Android&#8217;s Hilt library. Here&#8217;s how to use Koin Annotations in a multiplatform project. Basic usage Koin Annotations works with a KSP&#8230;&#160;<a href="https://www.jacobras.nl/2024/02/migrating-to-koin-annotations-in-a-multiplatform-project/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">Migrating to Koin Annotations in a multiplatform project</span></a>]]></description>
										<content:encoded><![CDATA[
<p>Koin is my favourite DI library, but out of the box it&#8217;s a bit verbose. With <a href="https://insert-koin.io/docs/reference/koin-annotations/start/" target="_blank" rel="noopener">Koin Annotations</a>, the overhead of manually writing <code>module{}</code> declarations is removed, making it as easy to use as Android&#8217;s Hilt library. Here&#8217;s how to use Koin Annotations in a multiplatform project.</p>



<h2 class="wp-block-heading" id="basic-usage">Basic usage</h2>



<p>Koin Annotations works with a KSP processor that scans for specific annotations and then creates a <code>module{}</code> declaration, as if you would normally write manually. If you&#8217;re not familiar with KSP, just know it&#8217;s a compiler plugin mechanism that allows for code generation.</p>



<p>I&#8217;ll assume starting with a project that&#8217;s already configured with Koin, but this article can also serve as a first start guide. Feel free to reply if there are any questions.</p>



<h3 class="wp-block-heading" id="1-configuring-the-build">1: Configuring the build</h3>



<p>First, declare the libraries:</p>



<pre title="libs.versions.toml" class="wp-block-code"><code lang="bash" class="language-bash">[versions]
koin = "3.5.3"
koin-ksp = "1.3.1"
ksp = "1.9.22-1.0.17"

[libraries]
koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-ksp" }
koin-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-ksp" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }

[plugins]
ksp = { id ="com.google.devtools.ksp", version.ref = "ksp" }

</code></pre>



<p>Then use these dependencies:</p>



<pre title="build.gradle.kts" class="wp-block-code"><code lang="kotlin" class="language-kotlin line-numbers">plugins {
    alias(libs.plugins.ksp)
}

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.koin.annotations)
            implementation(libs.koin.core)
        }
    }
}

dependenies {
    add("kspCommonMainMetadata", libs.koin.compiler)
    // Other compilation targets should be added here, see up in article.
}</code></pre>



<p>For Android, also add <code>implementation(libs.koin.android)</code> in <code>androidMain.dependencies{}</code>.</p>



<p class="article_info_box">In older Kotlin/KSP versions, it was necessary to manually add the KSP output as a source directory with <code>kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")</code>. That&#8217;s no longer required as it&#8217;s automatically configured now. Same goes for the <code>dependsOn kspCommonMainKotlinMetadata</code> workaround you might find online. That&#8217;s why it&#8217;s recommended to use the latest dependencies.</p>



<h3 class="wp-block-heading" id="2-replace-add-the-modules-to-modules">2: Replace/add the modules to @Modules</h3>



<p>Koin Annotations works by adding a class annotated with <code>@Module</code> in the same gradle module as where the the things to be injected also reside. Here&#8217;s how that looks.</p>



<p>You might currently have a manually written module:</p>



<pre title="[my-module/src/commonMain/kotlin]" class="wp-block-code"><code lang="kotlin" class="language-kotlin">val myModule = module {
    single&lt;MyRepository&gt; { MyRepositoryImpl(get(), get(), get()) }
    singleOf(::AnotherClass)
    factoryOf(::MoreThings)
    factoryOf(::OneLinePerClass)
}</code></pre>



<p>This can be replaced with:</p>



<pre title="[my-module/src/commonMain/kotlin]" class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Module
@ComponentScan
class MyModule</code></pre>



<p>Now any class/function that we annotate with <code>@Factory</code> (for factory instances) or <code>@Single</code> (for singletons) inside this gradle module will be picked up by the KSP processor.</p>



<pre title="[my-module/src/commonMain/kotlin]" class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Single
class MyRepositoryImpl(
    private val somethingInjected: AnotherClass
) : MyRepository</code></pre>



<p>Note that it&#8217;s not needed to specify that <code>MyRepositoryImpl</code> should be provided when the <code>MyRepository</code> interface is requested to be injected somewhere. Koin binds it automatically. So with this setup, the only thing needed to add something to DI is just one annotation in most cases.</p>



<p>Also, if you&#8217;re used to Hilt, you might be tempted to write <code>@Inject constructor</code>. That&#8217;s not needed with Koin, the single annotation above the class is enough.</p>



<p class="article_info_box"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>@ComponentScan</code> works by scanning from the current package into deeper packages. If you place your DI stuff in a package <code>com.example.mymodule.di</code>, then it won&#8217;t find code in the package <code>com.example.mymodule</code>, even though it&#8217;s in the same gradle module. In that case, you can instruct the processor on where it should look by changing the annotation to <code>@ComponentScan("com.example.mymodule")</code>.</p>



<h3 class="wp-block-heading" id="3-use-the-generated-modules">3: Use the generated modules</h3>



<p>After a build, the generated modules are available and we can use them in our Koin init. We can check out that generated code to see if everything went well so far! It&#8217;s easy to check what&#8217;s being generated by navigating to <code>/build/generated/ksp/[target]/kotlin</code>. Here&#8217;s how it looks in one of my projects:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/02/image-4.png"><img loading="lazy" decoding="async" width="1024" height="638" src="https://www.jacobras.nl/wp-content/uploads/2024/02/image-4-1024x638.png" alt="" class="wp-image-1331" srcset="https://www.jacobras.nl/wp-content/uploads/2024/02/image-4-1024x638.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/02/image-4-300x187.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/02/image-4-768x478.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/02/image-4.png 1257w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">I&#8217;m glad I don&#8217;t need to write all those definitions manually anymore, as this is just one of 30+ gradle modules, each with their own Koin module!</figcaption></figure></div>


<p>Now let&#8217;s reference the generated module. Locate your <code>startKoin</code> function call and update it from this:</p>



<pre title="Before" class="wp-block-code"><code lang="kotlin" class="language-kotlin">import org.koin.core.context.startKoin

startKoin { modules(myModule) }</code></pre>



<p>to this:</p>



<pre title="After" class="wp-block-code"><code lang="kotlin" class="language-kotlin">import org.koin.core.context.startKoin
import org.koin.ksp.generated.module // Import for the generated modules

startKoin { modules(MyModule().module) }</code></pre>



<p>Note that the module is referenced as a class instantiation <code>MyModule()</code> and we then use <code>.module</code> on it to get the generated module. This is because the code Koin generates is placed in the <code>org.koin.ksp.generated.module</code> package.</p>



<p>Now run the app and see the result. That&#8217;s all there is to it!</p>



<p><strong>To summarise:</strong></p>



<ol>
<li>Add KSP + Koin Annotations processor</li>



<li>Replace manual modules with <code>@Module</code>/<code>@ComponentScan</code> + annotate classes with <code>@Single</code>/<code>@Factory</code></li>



<li>Reference the generated modules from your Koin start method</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Advanced usage</h2>



<h3 class="wp-block-heading" id="nesting-modules">Including Modules</h3>



<p>You can include Modules in each other. For example, a <code>NetworkModule</code> might include a separate module that provides Json (de)serialisation support. Including is done through the annotation:</p>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Module(includes = [JsonModule::class]
@ComponentScan
class NetworkModule</code></pre>



<p>Now only including <code>NetworkModule</code> is enough to also get the content of <code>JsonModule</code> in the DI graph.</p>



<h3 class="wp-block-heading">Extra configuration in Modules</h3>



<p>If a class requires something that cannot be automatically provided, you can always manually write the code.</p>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Module
class MyModule {
    
    @Single
    fun provideSomething(dep: SomeDependency): Something {
        return Something(dep, "extraConfig")
    }
}</code></pre>



<h3 class="wp-block-heading">Extra configuration in methods</h3>



<p>Alternatively, you can also just write a function marked with one of the annotations.</p>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Module
@ComponentScan
class MyModule

@Single
internal fun provideSomething(dep: SomeDependency): Something {
    // Configuration here...
}</code></pre>



<p>It needs to be in the same gradle module, but not in the same file. This will be picked up by the processor and result in generated code that looks something like the following:</p>



<p><code>public val MyModule.module: Module = module { single() { Something (dep=get()) } }</code></p>



<h3 class="wp-block-heading" id="platform-specific-modules">Platform-specific code</h3>



<p>If you have platform-specific code, you need to run the KSP processor on that target (see <a href="https://www.jacobras.nl/2024/02/using-ksp-with-kotlin-multiplatform-a-quick-overview/" data-type="post" data-id="1202">Using KSP with KMP: Basic KSP API</a>). The generated module will only be available inside source sets with the same target.</p>



<p>For example, inside <code>[myModule/src/commonMain/kotlin]</code> you can have this:</p>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Module
@ComponentScan
class NetworkModule</code></pre>



<p>And inside the platform specific module for Android <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" />:</p>



<pre title="[myModule/src/androidMain/kotlin]" class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Single
internal fun provideAndroidHttpClient(context: Context) = HttpClient {
    // Extra configuration here...
}</code></pre>



<p>and for iOS <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f34e.png" alt="🍎" class="wp-smiley" style="height: 1em; max-height: 1em;" />:</p>



<pre title="[myModule/src/iosMain/kotlin]" class="wp-block-code"><code lang="kotlin" class="language-kotlin">@Single
internal fun provideAppleHttpClient() = HttpClient {
    // Extra configuration here...
}</code></pre>



<p>If you now compile the project on Android, <code>NetworkModule</code> will be generated in <code>build/generated/ksp/<strong>android</strong>/<strong>androidDebug</strong>/kotlin/org.koin.ksp.generated/NetworkModuleGen[package].kt</code>.</p>



<p>And if you compile the project on iOS, <code>NetworkModule</code> will be generated in <code>build/generated/ksp/<strong>IosSimulatorArm64</strong>/<strong>IosSimulatorArm64Main</strong>/kotlin/org.koin.ksp.generated/NetworkModuleGen[package].kt</code>.</p>



<p>Any annotated classes inside the [common] code will be available in both platform-specific variants of the module.</p>



<p>If on a higher level you have an <code>AndroidModule</code> and <code>IOSModule</code>, you can simply refer the common module declaration by writing <code>includes = [NetworkModule::class]</code> and the right platform specific generated one will be used automatically.</p>



<p>For Android, if you use <code>koin-android</code> and include <code>androidContext(this@Application)</code> in your <code>startKoin{}</code> block, then you can inject <code>Context</code> anywhere you need it, as its provided by default.</p>



<h3 class="wp-block-heading" id="providing-view-models-and-work-manager-workers-on-android">ViewModel and WorkManager on Android</h3>



<p>If you&#8217;re on Android, you might also have <code>ViewModel</code>s and <code>Worker</code>s you&#8217;d like to easily inject using annotations. The process is similar as above, with a difference in the annotations required. Instead of <code>@Single</code> and <code>@Factory</code>:</p>



<ul>
<li>Use <code>@KoinViewModel</code> for <code>ViewModel</code>s</li>



<li>Use <code>@KoinWorker</code> for <code>Worker</code>s</li>
</ul>



<p>These two are available by default in the Koin Annotations library, but note that they&#8217;re only available in the <code>androidMain</code> source set.</p>



<h3 class="wp-block-heading">Scoping modules</h3>



<p>Like Koin&#8217;s core library, Koin Annotations also has support for scoping with a special <code>@Scope</code> and <code>@Scoped</code> annotation, but those are .. out of scope for this article <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f941.png" alt="🥁" class="wp-smiley" style="height: 1em; max-height: 1em;" />. See <a href="https://insert-koin.io/docs/reference/koin-annotations/scope" target="_blank" rel="noopener">Scopes in Koin Annotations</a>.</p>



<h3 class="wp-block-heading">Parameters</h3>



<p>If you need access to dependencies only accessible on runtime, you can use Koin&#8217;s parameter mechanism. The annotation is called <code>@InjectedParam</code> and its usage is explained here: <a href="https://insert-koin.io/docs/reference/koin-annotations/definitions/#injected-parameters-with-injectedparam" target="_blank" rel="noopener">Injected Parameters</a>.</p>



<h2 class="wp-block-heading" id="further-reading">Further reading</h2>



<ul>
<li><a href="https://insert-koin.io/docs/reference/koin-annotations/definitions" target="_blank" rel="noopener">Definitions with Koin Annotations</a></li>



<li><a href="https://insert-koin.io/docs/reference/koin-annotations/scope" target="_blank" rel="noopener">Scopes in Koin Annotations</a></li>
</ul>



<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/migrating-to-koin-annotations-in-a-multiplatform-project-1e83ba3b5988" target="_blank" rel="noopener">https://medium.com/@jacobras/migrating-to-koin-annotations-in-a-multiplatform-project-1e83ba3b5988</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Using KSP with Kotlin Multiplatform: a quick overview</title>
		<link>https://www.jacobras.nl/2024/02/using-ksp-with-kotlin-multiplatform-a-quick-overview/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Tue, 27 Feb 2024 14:33:19 +0000</pubDate>
				<category><![CDATA[Kotlin Multiplatform]]></category>
		<category><![CDATA[Kotlin]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1202</guid>

					<description><![CDATA[KSP, Kotlin Symbol Processing, is the successor to kapt. It is used by several libraries to generate code, like Room and kotlinx-serialization. This article provides an overview on how to use external KSP processors in general. If you want to see one in action, check out Migrating to Koin Annotations in a multiplatform project. Basic&#8230;&#160;<a href="https://www.jacobras.nl/2024/02/using-ksp-with-kotlin-multiplatform-a-quick-overview/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">Using KSP with Kotlin Multiplatform: a quick overview</span></a>]]></description>
										<content:encoded><![CDATA[
<p>KSP, Kotlin Symbol Processing, is the successor to <code>kapt</code>. It is used by several libraries to generate code, like Room and kotlinx-serialization. This article provides an overview on how to use external KSP processors in general. If you want to see one in action, check out <a href="https://www.jacobras.nl/2024/02/migrating-to-koin-annotations-in-a-multiplatform-project/" data-type="post" data-id="1284">Migrating to Koin Annotations in a multiplatform project</a>.</p>



<h2 class="wp-block-heading">Basic KSP API</h2>



<p>First, add the compiler plugin to your version catalog:</p>



<pre title="libs.versions.toml" class="wp-block-code"><code lang="bash" class="language-bash">[plugins]
ksp = { id ="com.google.devtools.ksp", version = "1.9.22-1.0.17" }</code></pre>



<p>Use the latest release (<a href="https://github.com/google/ksp/releases" target="_blank" rel="noopener">https://github.com/google/ksp/releases</a>) for the Kotlin version you&#8217;re using, in this example KSP 1.0.17 for Kotlin 1.9.22.</p>



<p>Then we can use it. One confusing thing is that normally, we have all our dependencies together in a source set&#8217;s <code>dependencies{}</code> block. KSP processors, however, need to be configured outside of the source sets. Here&#8217;s how that looks:</p>



<pre title="build.gradle.kts" class="wp-block-code"><code lang="kotlin" class="language-kotlin line-numbers">plugins {
    alias(libs.plugins.ksp)
}

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.my.other.dependencies)
            // Not here...
        }
    }
}

dependenies {
    // ... but instead: here!
    add("kspCommonMainMetadata", libs.some.ksp.plugin) // Run KSP on [commonMain] code
    add("kspAndroid", libs.some.ksp.plugin)
    add("kspIosX64", libs.some.ksp.plugin)
    add("kspIosArm64", libs.some.ksp.plugin)
    add("kspIosSimulatorArm64", libs.some.ksp.plugin)
}</code></pre>



<p>Note that there&#8217;s a line for each compilation target. Also note that the name for common code here is <code>kspCommonMainMetadata</code>. If you only want to run the annotation processor on common code and not on any target-specific code, than the lines following it can be omitted.</p>



<p>That&#8217;s all there is to it! You can now use the generated code. It&#8217;s easy to check what&#8217;s being generated by navigating to <code>/build/generated/ksp/[target]/kotlin</code>:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://www.jacobras.nl/wp-content/uploads/2024/02/image-2.png"><img loading="lazy" decoding="async" width="638" height="359" src="https://www.jacobras.nl/wp-content/uploads/2024/02/image-2.png" alt="" class="wp-image-1236" srcset="https://www.jacobras.nl/wp-content/uploads/2024/02/image-2.png 638w, https://www.jacobras.nl/wp-content/uploads/2024/02/image-2-300x169.png 300w" sizes="(max-width: 638px) 100vw, 638px" /></a><figcaption class="wp-element-caption">Generated KSP output (from Koin Annotations) in <code>/build/generated/ksp/android/androidDebug/kotlin</code></figcaption></figure></div>


<p class="article_info_box">In older Kotlin/KSP versions, it was necessary to manually add the KSP output as a source directory with <code>kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")</code>. That&#8217;s no longer required as it&#8217;s automatically configured now. Same goes for the <code>dependsOn kspCommonMainKotlinMetadata</code> workaround you might find online. That&#8217;s why it&#8217;s recommended to use the latest dependencies.</p>



<h2 class="wp-block-heading">Further reading</h2>



<p>For more advanced usage with the example of Koin Annotations, see <a href="https://www.jacobras.nl/2024/02/migrating-to-koin-annotations-in-a-multiplatform-project/" data-type="post" data-id="1284">Migrating to Koin Annotations in a multiplatform project</a>.</p>



<ul>
<li><a href="https://kotlinlang.org/docs/ksp-overview.html" target="_blank" rel="noopener">kotlinlang.org: KSP Overview</a></li>
</ul>



<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/using-ksp-with-kotlin-multiplatform-a-quick-overview-6b858df77b5f" target="_blank" rel="noopener">https://medium.com/@jacobras/using-ksp-with-kotlin-multiplatform-a-quick-overview-6b858df77b5f</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Getting the native iOS look &#038; feel in your Compose Multiplatform app</title>
		<link>https://www.jacobras.nl/2024/02/native-ios-look-feel-compose-multiplatform/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Sat, 17 Feb 2024 18:39:42 +0000</pubDate>
				<category><![CDATA[Kotlin Multiplatform]]></category>
		<category><![CDATA[Compose]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=993</guid>

					<description><![CDATA[You can reply to this article at https://medium.com/@jacobras/getting-the-native-ios-look-feel-in-your-compose-multiplatform-app-33371e6ad362. Compose&#8217; default look and feel is that of Material Design. In Compose Multiplatform, certain elements have been tweaked on iOS to feel more native. For example, since version 1.5 the scroll effect on iOS has been made to imitate that of the platform. However, most of the&#8230;&#160;<a href="https://www.jacobras.nl/2024/02/native-ios-look-feel-compose-multiplatform/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">Getting the native iOS look &#038; feel in your Compose Multiplatform app</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/getting-the-native-ios-look-feel-in-your-compose-multiplatform-app-33371e6ad362" target="_blank" rel="noopener">https://medium.com/@jacobras/getting-the-native-ios-look-feel-in-your-compose-multiplatform-app-33371e6ad362</a>.</p>



<p>Compose&#8217; default look and feel is that of Material Design. In Compose Multiplatform, certain elements have been tweaked on iOS to feel more native. For example, since <a href="https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.5.0-beta01" data-type="link" data-id="https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.5.0-beta01" target="_blank" rel="noopener">version 1.5</a> the scroll effect on iOS has been made to imitate that of the platform. However, most of the UI elements still look Material. Let&#8217;s take a look at an easy way to obtain more of the iOS native look &amp; feel in your app.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1000" height="500" src="https://www.jacobras.nl/wp-content/uploads/2024/02/featured-3.jpg" alt="iOS simulator on top of Android emulator, showing native look &amp; feel on each platform" class="wp-image-1130" style="width:566px;height:auto" srcset="https://www.jacobras.nl/wp-content/uploads/2024/02/featured-3.jpg 1000w, https://www.jacobras.nl/wp-content/uploads/2024/02/featured-3-300x150.jpg 300w, https://www.jacobras.nl/wp-content/uploads/2024/02/featured-3-768x384.jpg 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure></div>


<p>We&#8217;re going to use a library called <a href="https://github.com/alexzhirkevich/compose-cupertino" target="_blank" rel="noopener">Compose Cupertino</a>. It&#8217;s available in several flavours:</p>



<ul>
<li><code>cupertino</code>: iOS-like widgets, built using Compose;</li>



<li><code>cupertino-native</code>: wrappers around native UIKit components;</li>



<li><code>cupertino-adaptive</code>: adaptive themes/wrappers that use Material Design on Android and the iOS-like widgets from <code>cupertino</code> and some widgets from <code>cupertino-native</code> on iOS (main focus of this article);</li>



<li><code>cupertino-icons-extended</code>: more than 800 of the most used <a href="https://developer.apple.com/sf-symbols/" target="_blank" rel="noopener">Apple SF Symbols</a> (note: these are copyrighted and require adhering to a license agreement);</li>



<li><code>cupertino-decompose</code>: native feel of screen transitions &amp; swipe gestures.</li>
</ul>



<p>In this article, we&#8217;re going to see how easy it is to use the Adaptive flavour to improve our apps&#8217; system bars (top bar + navigation/tab bar) and main components (buttons + loading indicators + dialogs). There&#8217;s a lot more in the library, so take this post as an introduction to it and not a complete guide. We&#8217;re also going to see how we can test the iOS look on Android!</p>



<p class="article_warning_box"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Warning</strong>: Composer Cupertino is in the experimental phase. All APIs can change in an incompatible way or even be dropped at any point in time. This tutorial was written for version <code>0.1.0-alpha03</code>.</p>



<h2 class="wp-block-heading">How does it work?</h2>



<p>The native looking widgets in Cupertino are completely rebuilt iOS components using Compose. What this means is that they&#8217;re not actual native components, but rather drawn to look like them. Should we be worried about that? I wouldn&#8217;t, because that&#8217;s similar to how Compose itself rebuilds Android components. Those are also being drawn on canvas instead of relying on legacy <code>android.view</code> components.</p>



<p>Cupertino Adaptive is built not only on top of the Material components, but also with their API in mind. This means that many Material components you&#8217;re using now can be switched out for their Adaptive counterparts in seconds. They&#8217;ll still be calling the exact same underlying code on Android, but on iOS they&#8217;ll be drawn to look like native components. The exception to these are the adaptive widgets ending in <code>*Native</code>, like <code>AdaptiveAlertDialogNative</code>. That one calls the wrappers from Cupertino Native, which invoke the actual UIKit component for a dialog.</p>



<p>Let&#8217;s see it in action! All code is available at <a href="https://github.com/jacobras/ComposeCupertinoSample" target="_blank" rel="noopener">https://github.com/jacobras/ComposeCupertinoSample</a>.</p>



<h2 class="wp-block-heading">Tutorial: Material to Cupertino Adaptive</h2>



<p>The sample project we&#8217;ll be using has been created with the <a href="https://kmp.jetbrains.com/" target="_blank" rel="noopener">Kotlin Multiplatform Wizard</a>. I added a scaffold with a toolbar, two tabs, a loading indicator and a dialog, all Material3 components. You can view the starting point codebase here: <a href="https://github.com/jacobras/ComposeCupertinoSample/tree/starting-point" target="_blank" rel="noopener">ComposeCupertinoSample/tree/starting-point</a>.</p>



<h3 class="wp-block-heading">1: Adding the dependency</h3>



<p>We add the dependency to our version catalog and implement it in the app:</p>



<pre title="" class="wp-block-code"><code lang="kotlin" class="language-kotlin">// in gradle/libs.versions.toml:
cupertino = { module = "io.github.alexzhirkevich:cupertino-adaptive", version = "0.1.0-alpha03" }

// in composeApp/build.gradle.kts, inside common.dependencies:
implementation(libs.cupertino)</code></pre>



<p>Full commit: <a href="https://github.com/jacobras/ComposeCupertinoSample/pull/2/commits/d7b05ad809bc03cf87c3c58a6f7765f5c6442b92" target="_blank" rel="noopener">ComposeCupertinoSample/pull/2/commits/d7b05ad809bc03cf87c3c58a6f7765f5c6442b92</a></p>



<h3 class="wp-block-heading">2: Updating the theme</h3>



<p>The AppTheme currently uses MaterialTheme. We need to change that to use the adaptive theme. It has two important parameters: <code>material</code>, which takes our current MaterialTheme, and <code>cupertino</code>, which takes a <code>CupertinoTheme</code>. That one allows customising our iOS look by passing custom colours to <code>darkColorScheme()</code> or <code>lightColorScheme()</code>.</p>



<pre title="composeApp/src/common/kotlin/AppTheme.kt" class="wp-block-code"><code lang="kotlin" class="language-kotlin">// Before
@Composable
fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -&gt; Unit
) {
    MaterialTheme(
        colorScheme = if (useDarkTheme) {
            darkColorScheme()
        } else {
          lightColorScheme()
        },
        content = content
    )
}

// After
@OptIn(ExperimentalAdaptiveApi::class)
@Composable
fun AppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    theme: Theme = determineTheme(),
    content: @Composable () -&gt; Unit
) {
    AdaptiveTheme(
        material = {
            MaterialTheme(
                colorScheme = if (useDarkTheme) {
                    androidx.compose.material3.darkColorScheme()
                } else {
                    androidx.compose.material3.lightColorScheme()
                },
                content = it
            )
        },
        cupertino = {
            CupertinoTheme(
                colorScheme = if (useDarkTheme) {
                    darkColorScheme()
                } else {
                    lightColorScheme()
                },
                content = it
            )

        },
        target = theme,
        content = content
    )
}</code></pre>



<p>The method <code>determineTheme()</code> is an expect/actual function that returns <code>Theme.Material</code> from [androidMain] and <code>Theme.Cupertino</code> from [iosMain]. See the full commit for details: <a href="https://github.com/jacobras/ComposeCupertinoSample/pull/2/commits/592b3e2a1d35ff8a9961dbc6739e0e25bf581b95" target="_blank" rel="noopener">ComposeCupertinoSample/pull/2/commits/592b3e2a1d35ff8a9961dbc6739e0e25bf581b95</a></p>



<p class="article_info_box">From the next version of Cupertino the <code>Theme</code> will be determined automatically, making the expect/actual function above redundant. </p>



<p>If we run the app now, nothing changes yet. Everything looks exactly like before, because we haven&#8217;t used any adaptive components yet. This demonstrates that on Android, everything will remain the same.</p>



<h3 class="wp-block-heading">3: Using adaptive components</h3>



<p>Now comes the fun part! This is also the easiest change. We locate all material components and replace them with the adaptive wrappers. An example:</p>



<pre title="composeApp/src/commonMain/kotlin/MainTab.kt" class="wp-block-code"><code lang="kotlin" class="language-kotlin">// Before
Button(onClick = { showContent = !showContent }) {
    Text("Click me!")
}

// After
AdaptiveButton(onClick = { showContent = !showContent }) {
    Text("Click me!")
}</code></pre>



<p>We&#8217;re going to change these other components as well:</p>



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th>Material component<br>(Android look)</th><th>Cupertino component<br>(iOS look)</th><th>Adaptive component<br>(Android/iOS look)</th></tr></thead><tbody><tr><td><code>Scaffold()</code></td><td><code><strong>Cupertino</strong>Scaffold</code></td><td><code><strong>Adaptive</strong>Scaffold</code></td></tr><tr><td><code>TopAppBar()</code></td><td><code><strong>Cupertino</strong>TopAppBar</code></td><td><code><strong>Adaptive</strong>TopAppBar</code></td></tr><tr><td><code>NavigationBar()</code><br><code>NavigationBarItem()</code></td><td><code><strong>Cupertino</strong>NavigationBar</code><br><code><strong>Cupertino</strong>NavigationBarItem</code></td><td><code><strong>Adaptive</strong>NavigationBar</code><br><code><strong>Adaptive</strong>NavigationBarItem</code></td></tr><tr><td><code>Button()</code></td><td><code><strong>Cupertino</strong>Button</code></td><td><code><strong>Adaptive</strong>Button</code></td></tr><tr><td><code>CircularProgressIndicator()</code></td><td><code><strong>Cupertino</strong>CircularProgressIndicator</code></td><td><code><strong>Adaptive</strong>CircularProgressIndicator</code></td></tr><tr><td><code>AlertDialog()</code></td><td><code><strong>Cupertino</strong>AlertDialog</code></td><td><code><strong>Adaptive</strong>AlertDialog</code></td></tr></tbody></table></figure>



<p>The pattern should be clear: <code><strong>Cupertino</strong><em>[ComponentName]</em></code> for the iOS style components and <code><strong>Adaptive</strong><em>[ComponentName]</em></code> for the ones that switch based on the platform. For this tutorial, we&#8217;ll use all the adaptive ones.</p>



<p>Most of these just require changing the name without changing the parameters. The <code>AlertDialog</code> is an exception, which requires changing <code>text</code> to <code>title</code> and <code>confirmButton</code> to <code>buttons</code>.</p>



<p>Full commit: <a href="https://github.com/jacobras/ComposeCupertinoSample/pull/2/commits/a8da43dd7db1187df15c0fbbca9af3ef705c64bd" target="_blank" rel="noopener">ComposeCupertinoSample/pull/2/commits/a8da43dd7db1187df15c0fbbca9af3ef705c64bd</a></p>



<p>If we now run the app again on iOS, we see the following (left simulator shows before, right simulator shows after the changes):</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2.jpg"><img loading="lazy" decoding="async" width="1024" height="747" src="https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2-1024x747.jpg" alt="iOS simulators next to each other, showing before and after of using Compose Cupertino" class="wp-image-1119" srcset="https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2-1024x747.jpg 1024w, https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2-300x219.jpg 300w, https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2-768x561.jpg 768w, https://www.jacobras.nl/wp-content/uploads/2024/02/iOS-before-and-after-2.jpg 1314w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>That looks amazing! Dark theme also works on both platforms:</p>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2.jpg"><img loading="lazy" decoding="async" width="1024" height="724" src="https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2-1024x724.jpg" alt="Android and iOS emulators next to each other, showing native look &amp; feel on each platform. This time in dark theme." class="wp-image-1118" srcset="https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2-1024x724.jpg 1024w, https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2-300x212.jpg 300w, https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2-768x543.jpg 768w, https://www.jacobras.nl/wp-content/uploads/2024/02/dark-theme-comparison-2.jpg 1357w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<h3 class="wp-block-heading">Testing on Android</h3>



<p>To test the Cupertino look on Android, all that&#8217;s required is changing the <code>determineTheme()</code> method in the Android source set:</p>



<pre title="[android] src/androidMain/kotlin" class="wp-block-code"><code lang="kotlin" class="language-kotlin">actual fun determineTheme(): Theme = Theme.Material3</code></pre>



<h2 class="wp-block-heading">Further steps &amp; reading</h2>



<p>I hope it&#8217;s clear how easy this was and how big the impact is. There&#8217;s more we can do: using adaptive icons (so we get the iOS ones on iPhone/iPad) or use more native-looking components, but that&#8217;s up to you. This has been just a short introduction to getting started with the library.</p>



<ul>
<li><a href="https://github.com/alexzhirkevich/compose-cupertino" target="_blank" rel="noopener">GitHub: Compose Cupertino</a></li>



<li><a href="https://github.com/jacobras/ComposeCupertinoSample" target="_blank" rel="noopener">GitHub: Compose Cupertino sample app</a></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Talk: “Compiler Plugins and Compose Multiplatform”</title>
		<link>https://www.jacobras.nl/2024/02/talk-compiler-plugins-and-compose-multiplatform/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Thu, 15 Feb 2024 11:47:00 +0000</pubDate>
				<category><![CDATA[Talks]]></category>
		<category><![CDATA[Kotlin]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=1639</guid>

					<description><![CDATA[Flexibility is one of the key characteristics of Kotlin Multiplatform. It means you can use it where needed, but also that shared code can live next to platform-specific code in the same codebase. Let’s see how that works in practice by taking a well-known app and gradually adopting/migrating it to Kotlin &#038; Compose Multiplatform.]]></description>
										<content:encoded><![CDATA[
<p>February, 2024, at employer’s internal Android guild.</p>



<p>Flexibility is one of the key characteristics of Kotlin Multiplatform. It means you can use it where needed, but also that shared code can live next to platform-specific code in the same codebase. Let’s see how that works in practice by taking a well-known app and gradually adopting/migrating it to Kotlin &amp; Compose Multiplatform.</p>



<p>Slides (with company references removed):</p>



<iframe loading="lazy" src="https://docs.google.com/presentation/d/e/2PACX-1vRiw9NsE-Yj5UPR_GGGpVUgEQQQLRi0XJQ-s6qBNyJG332QSvZxEBUczwTCV0l6PgvBu0oJQLmDegyz/embed?start=false&#038;loop=false&#038;delayms=3000" frameborder="0" width="100%" height="500" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>🆕 ComposeActionMenu: now multiplatform</title>
		<link>https://www.jacobras.nl/2024/01/composeactionmenu-now-multiplatform/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Thu, 11 Jan 2024 23:36:49 +0000</pubDate>
				<category><![CDATA[Compose]]></category>
		<category><![CDATA[Kotlin Multiplatform]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=983</guid>

					<description><![CDATA[You can reply to this article at https://medium.com/@jacobras/composeactionmenu-now-multiplatform-178bc61d8b36. My ComposeActionMenu library, which provides an easy-to-use action menu like the Android Options Menu, is now built with Compose Multiplatform. The supported targets are Android (like before), iOS and JVM (desktop). Features GitHub https://github.com/jacobras/ComposeActionMenu]]></description>
										<content:encoded><![CDATA[
<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/composeactionmenu-now-multiplatform-178bc61d8b36" target="_blank" rel="noopener">https://medium.com/@jacobras/composeactionmenu-now-multiplatform-178bc61d8b36</a>.</p>



<p>My <a href="https://github.com/jacobras/ComposeActionMenu" target="_blank" rel="noopener">ComposeActionMenu library</a>, which provides an easy-to-use action menu like the <a href="https://developer.android.com/develop/ui/views/components/menus#options-menu" target="_blank" rel="noopener">Android Options Menu</a>, is now built with Compose Multiplatform. The supported targets are Android (like before), iOS and JVM (desktop).</p>



<h2 class="wp-block-heading">Features</h2>



<ul>
<li>Icons (optional)</li>



<li>Selectable/checkable items</li>



<li>Nested sub menus</li>



<li>Automatic overflow for items that don&#8217;t fit the specified maximum</li>
</ul>



<figure class="wp-block-image size-large"><a href="https://www.jacobras.nl/wp-content/uploads/2024/01/image.png"><img loading="lazy" decoding="async" width="1024" height="667" src="https://www.jacobras.nl/wp-content/uploads/2024/01/image-1024x667.png" alt="" class="wp-image-984" srcset="https://www.jacobras.nl/wp-content/uploads/2024/01/image-1024x667.png 1024w, https://www.jacobras.nl/wp-content/uploads/2024/01/image-300x195.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/01/image-768x500.png 768w, https://www.jacobras.nl/wp-content/uploads/2024/01/image.png 1459w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><figcaption class="wp-element-caption">Desktop and Android example</figcaption></figure>



<h2 class="wp-block-heading">GitHub</h2>



<p><a href="https://github.com/jacobras/ComposeActionMenu" target="_blank" rel="noopener">https://github.com/jacobras/ComposeActionMenu</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>🆕 HumanReadable: data formatting utilities for KMP</title>
		<link>https://www.jacobras.nl/2024/01/humanreadable-data-formatting-utilities-kmp/</link>
		
		<dc:creator><![CDATA[Jacob Ras]]></dc:creator>
		<pubDate>Wed, 10 Jan 2024 13:38:45 +0000</pubDate>
				<category><![CDATA[Kotlin Multiplatform]]></category>
		<guid isPermaLink="false">https://www.jacobras.nl/?p=950</guid>

					<description><![CDATA[You can reply to this article at https://medium.com/@jacobras/humanreadable-data-formatting-utilities-for-kmp-3e51629c95bc. I made a small Kotlin Multiplatform library that allows formatting relative dates and filesizes. It&#8217;s called HumanReadable and it&#8217;s available on GitHub: https://github.com/jacobras/Human-Readable. In the current version, 1.2.0, the following 11 languages are supported: Czech, Dutch, English (default), French, German, Italian, Indonesian, Russian, Spanish, Turkish and Ukrainian.&#8230;&#160;<a href="https://www.jacobras.nl/2024/01/humanreadable-data-formatting-utilities-kmp/" class="" rel="bookmark">Read More &#187;<span class="screen-reader-text">🆕 HumanReadable: data formatting utilities for KMP</span></a>]]></description>
										<content:encoded><![CDATA[
<p class="article_medium_box">You can reply to this article at <a href="https://medium.com/@jacobras/humanreadable-data-formatting-utilities-for-kmp-3e51629c95bc" target="_blank" rel="noopener">https://medium.com/@jacobras/humanreadable-data-formatting-utilities-for-kmp-3e51629c95bc</a>.</p>



<figure class="wp-block-image size-full"><a href="https://www.jacobras.nl/wp-content/uploads/2024/01/HumanReadable.png"><img loading="lazy" decoding="async" width="1000" height="500" src="https://www.jacobras.nl/wp-content/uploads/2024/01/HumanReadable.png" alt="" class="wp-image-978" srcset="https://www.jacobras.nl/wp-content/uploads/2024/01/HumanReadable.png 1000w, https://www.jacobras.nl/wp-content/uploads/2024/01/HumanReadable-300x150.png 300w, https://www.jacobras.nl/wp-content/uploads/2024/01/HumanReadable-768x384.png 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></a></figure>



<p>I made a small Kotlin Multiplatform library that allows formatting relative dates and filesizes. It&#8217;s called HumanReadable and it&#8217;s available on GitHub: <a href="https://github.com/jacobras/Human-Readable" target="_blank" rel="noopener">https://github.com/jacobras/Human-Readable</a>.</p>



<p>In the current version, 1.2.0, the following 11 languages are supported: Czech, Dutch, English (<strong>default</strong>), French, German, Italian, Indonesian, Russian, Spanish, Turkish and Ukrainian. The library works with <a href="https://github.com/Kotlin/kotlinx-datetime" target="_blank" rel="noopener">kotlinx-datetime</a>.</p>



<h2 class="wp-block-heading">Features</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f570.png" alt="🕰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Relative time</h3>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">HumanReadable.timeAgo(now - 134.minutes) // "2 hours ago"
HumanReadable.timeAgo(now + 8.minutes) // "in 8 minutes"</code></pre>



<h3 class="wp-block-heading"><a href="https://github.com/jacobras/Human-Readable?tab=readme-ov-file#%EF%B8%8F-duration" target="_blank" rel="noopener"></a><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/23f1.png" alt="⏱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Duration</h3>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">HumanReadable.duration(5.seconds) // "5 seconds"
HumanReadable.duration(7.days) // "1 week"
HumanReadable.duration(544.hours) // "3 weeks"</code></pre>



<h3 class="wp-block-heading"><a href="https://github.com/jacobras/Human-Readable?tab=readme-ov-file#-file-size" target="_blank" rel="noopener"></a><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4c2.png" alt="📂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> File size</h3>



<pre class="wp-block-code"><code lang="kotlin" class="language-kotlin">HumanReadable.fileSize(333) // "333 B"
HumanReadable.fileSize(2_048, decimals = 1) // "2.0 kB"
HumanReadable.fileSize(21_947_282_882, decimals = 2) // "20.44 GB"</code></pre>



<h2 class="wp-block-heading">GitHub</h2>



<p><a href="https://github.com/jacobras/Human-Readable" target="_blank" rel="noopener">https://github.com/jacobras/Human-Readable</a></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
