<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Kittysplit Blog</title>
    <description></description>
    <link>https://blog.kittysplit.com/en/</link>
    <language>en</language>
    <atom:link href="https://blog.kittysplit.com/en/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Introducing Accounts - Your Kitties, Safe and in Sync</title>
        <description>&lt;p&gt;Accounts are here. Log in on Kittysplit (web, iOS, or Android) and all your Kitties come with you. It’s our biggest update in a while, and it’s completely optional.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026-04-23/accounts-title.png&quot; alt=&quot;Kittysplit accounts&quot; title=&quot;Kittysplit accounts&quot; /&gt;&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;why-accounts&quot;&gt;Why Accounts?&lt;/h2&gt;

&lt;p&gt;For a long time, Kittysplit worked without any accounts, and this was by design. No account means no registration needed, no passwords to remember, no personal data collected. Back then, we also &lt;a href=&quot;/webapp-vs-native-app&quot;&gt;didn’t have mobile apps&lt;/a&gt;, so switching devices was less of an issue.&lt;/p&gt;

&lt;p&gt;But accounts were the #1 request in our survey, and we’ve heard it often since the &lt;a href=&quot;/mobile-app-release&quot;&gt;mobile apps launched&lt;/a&gt; last year. So finally accounts are here! Shipping accounts isn’t that hard. Keeping them optional is where it gets interesting. It took a lot of careful planning to make sure a Kitty works the same whether you’re logged in or not.&lt;/p&gt;

&lt;h2 id=&quot;your-kitties-on-every-device&quot;&gt;Your Kitties, on every device&lt;/h2&gt;

&lt;p&gt;Sign in on the web or your phone and you’ll find the same Kitties everywhere. No more digging through old chats for the link. New phone? Switched browsers? Just log in and pick up where you left off.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2026-04-23/device_screen_shot_1.png&quot; alt=&quot;Log in or register&quot; title=&quot;Log in or register&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2026-04-23/device_screen_shot_2.png&quot; alt=&quot;Your profile&quot; title=&quot;Your profile&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Log in or register&lt;/em&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Your profile&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;safer-kitties&quot;&gt;Safer Kitties&lt;/h2&gt;

&lt;p&gt;If you create a Kitty while logged in, it’s yours. Only you can edit or delete it, so nothing important disappears by accident. Your friends can still add expenses and settle up just like before. Nothing changes for them.&lt;/p&gt;

&lt;h2 id=&quot;your-friends-dont-need-an-account&quot;&gt;Your friends don’t need an account&lt;/h2&gt;

&lt;p&gt;Accounts are opt-in, and they always will be. Invite people the way you always have: drop them the link, and they can start splitting right away. No sign-up, no friction. That’s still the heart of Kittysplit.&lt;/p&gt;

&lt;h2 id=&quot;coming-soon-profile-pictures&quot;&gt;Coming soon: profile pictures&lt;/h2&gt;

&lt;p&gt;Profile pictures are up next: upload a nice pic so you show up as more than a name in your friends’ Kitties. We’ve got a few more Accounts-only ideas in the pipeline too.&lt;/p&gt;

&lt;p&gt;Happy splitting! 🐱✨&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Have feedback or ideas? Reach out on &lt;a href=&quot;https://x.com/kittysplit&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/kittysplit.bsky.social&quot;&gt;Bluesky&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/kittysplit.app/&quot;&gt;Instagram&lt;/a&gt; or write an email to info[at]kittysplit.com&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/introducing-accounts/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/introducing-accounts/</guid>
      </item>
    
      <item>
        <title>Expense Attachments - Add Photos to Your Expenses</title>
        <description>&lt;p&gt;We’ve added expense attachments to Kittysplit! You can now attach up to 3 images to any expense in your Kitty. This is a &lt;strong&gt;Super Kitty&lt;/strong&gt; feature, so you’ll need to upgrade your Kitty to use it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026-03-12/expense-attachments-1.png&quot; alt=&quot;Expense attachments&quot; title=&quot;Expense attachments&quot; width=&quot;320&quot; /&gt;&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;whats-new&quot;&gt;What’s new?&lt;/h2&gt;

&lt;p&gt;Every expense can now have photos attached to it. Snap a picture of the receipt, the restaurant bill, or whatever you’re splitting, and it stays right there with the expense for everyone to see.&lt;/p&gt;

&lt;p&gt;You can add up to 3 images per expense, which should cover most situations. Think receipts, hotel invoices, concert tickets, or just a photo of what you bought so the group knows what they’re chipping in for.&lt;/p&gt;

&lt;p&gt;No more digging through your camera roll weeks later trying to find that one receipt. Attach it when you add the expense and you’re done.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;When you add or edit an expense, you’ll see an option to attach images. Pick one from your gallery. If you need to swap one out later, just remove it and add a new one.&lt;/p&gt;

&lt;h2 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h2&gt;

&lt;p&gt;Upgrade to a Super Kitty and attach photos to your next expense. That’s it!&lt;/p&gt;

&lt;p&gt;Happy splitting! 🐱✨&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Have feedback or ideas? Reach out on &lt;a href=&quot;https://x.com/kittysplit&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/kittysplit.bsky.social&quot;&gt;Bluesky&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/kittysplit.app/&quot;&gt;Instagram&lt;/a&gt; or write an email to info[at]kittysplit.com&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/expense-attachments/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/expense-attachments/</guid>
      </item>
    
      <item>
        <title>Introducing Expense Categories - Better Insights for Your Kitties</title>
        <description>&lt;p&gt;We’re happy to announce a powerful new feature for Kittysplit: &lt;strong&gt;Expense Categories&lt;/strong&gt;! Starting today, you can organize every expense and income in your Kitties with categories, giving you a clearer picture of where your money is going.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2026-02-11/expense-categories-1.png&quot; alt=&quot;Add expense&quot; title=&quot;Add expense&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2026-02-11/expense-categories-2.png&quot; alt=&quot;Filter by category&quot; title=&quot;Filter by category&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Add Expense&lt;/em&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Filter by category&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;whats-new&quot;&gt;What’s New?&lt;/h2&gt;

&lt;p&gt;Every expense or income you add to your Kitty can now be assigned to one of 12 categories:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Rent &amp;amp; Home&lt;/strong&gt; - Rent, mortgage, home insurance, and household essentials&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Accommodation&lt;/strong&gt; - Hotels, vacation rentals, and temporary stays&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Food &amp;amp; Groceries&lt;/strong&gt; - Supermarket runs and household food supplies&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dining &amp;amp; Drinks&lt;/strong&gt; - Restaurants, bars, cafes, and takeout&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transportation&lt;/strong&gt; - Gas, public transit, ride-shares, and car rentals&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Utilities &amp;amp; Connectivity&lt;/strong&gt; - Electricity, water, internet, and phone bills&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Health&lt;/strong&gt; - Medical expenses, pharmacy, and health insurance&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Experiences&lt;/strong&gt; - Entertainment, activities, concerts, and events&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Shopping &amp;amp; Gear&lt;/strong&gt; - Clothing, electronics, and general purchases&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Wellness &amp;amp; Personal Care&lt;/strong&gt; - Gym, spa, beauty, and self-care&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Financial &amp;amp; Fees&lt;/strong&gt; - Bank fees, service charges, and financial costs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Miscellaneous&lt;/strong&gt; - Everything else that doesn’t fit elsewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each category comes with its own distinctive color, making it easy to spot patterns at a glance.&lt;/p&gt;

&lt;h2 id=&quot;filter-your-expenses&quot;&gt;Filter Your Expenses&lt;/h2&gt;

&lt;p&gt;The first feature we’re rolling out with categories is &lt;strong&gt;expense filtering&lt;/strong&gt;. You can now filter your expense list by category to quickly find:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;All &lt;strong&gt;dining&lt;/strong&gt; expenses from your group trip&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Grocery&lt;/strong&gt; spending in your shared apartment&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transportation&lt;/strong&gt; costs for your road trip&lt;/li&gt;
  &lt;li&gt;Any specific category you want to review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it incredibly easy to answer questions like “How much did we spend on food?” or “What were our accommodation costs during the vacation?”&lt;/p&gt;

&lt;h2 id=&quot;whats-coming-next&quot;&gt;What’s Coming Next?&lt;/h2&gt;

&lt;p&gt;This is just the beginning. We’re already working on the next features to help you get even more value from your categorized expenses:&lt;/p&gt;

&lt;h3 id=&quot;month-to-month-comparisons&quot;&gt;Month-to-Month Comparisons&lt;/h3&gt;
&lt;p&gt;Track how your spending in each category changes over time. See if your grocery bills are creeping up or if you’re spending less on dining out than last month.&lt;/p&gt;

&lt;h3 id=&quot;trend-analysis&quot;&gt;Trend Analysis&lt;/h3&gt;
&lt;p&gt;Identify spending patterns and trends across weeks, months, or even years. Perfect for roommates who want to optimize their shared household budget.&lt;/p&gt;

&lt;h3 id=&quot;visual-reports&quot;&gt;Visual Reports&lt;/h3&gt;
&lt;p&gt;Beautiful charts and graphs showing your expense breakdown by category. Pie charts for overall distribution, bar graphs for category comparisons, and timeline views for spending trends.&lt;/p&gt;

&lt;h2 id=&quot;why-categories-matter&quot;&gt;Why Categories Matter&lt;/h2&gt;

&lt;p&gt;Whether you’re sharing an apartment with roommates or planning a group vacation categories help you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Understand spending patterns&lt;/strong&gt; - See where your shared money actually goes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Plan smarter&lt;/strong&gt; - Use historical data to budget for similar future events&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stay accountable&lt;/strong&gt; - Everyone in the group can see category breakdowns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;get-started-today&quot;&gt;Get Started Today&lt;/h2&gt;

&lt;p&gt;Categories are available now in the latest version of kittysplit. Simply add or edit an expense, and you’ll see the option to assign a category. It’s that simple!&lt;/p&gt;

&lt;p&gt;As always, we’d love to hear your feedback and suggestions for future improvements.&lt;/p&gt;

&lt;p&gt;Happy splitting! 🐱✨&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Got ideas for making categories work better for your use case? Drop us a line at &lt;a href=&quot;https://x.com/kittysplit&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/kittysplit.bsky.social&quot;&gt;Bluesky&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/kittysplit.app/&quot;&gt;Instagram&lt;/a&gt; or write an email to info[at]kittysplit.com&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/expense-categories/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/expense-categories/</guid>
      </item>
    
      <item>
        <title>Improved Overview and Cat Avatars</title>
        <description>&lt;p&gt;Just in time for Christmas 2025 the Kittysplit Apps are getting an update (v1.3), focusing on two improvements we’ve wanted to make for a while:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;A redesigned overview&lt;/strong&gt; that shows what’s relevant without drowning you in numbers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cat avatars&lt;/strong&gt; for a friendlier, more playful look&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;overview-before-and-after&quot;&gt;Overview: Before and After&lt;/h2&gt;

&lt;p&gt;The overview is what you see first when opening a Kitty. We’ve reworked it to answer your main questions quickly: What’s the total cost of the event? Who owes what? Overall we tried give everything more structure with the help of Material Design Cards. This goes even beyond the overview in some parts.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2025-12-09/kittysplit_app_overview_v1.png&quot; alt=&quot;Kitty Overview - before&quot; title=&quot;Kitty Overview - before&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2025-12-09/kittysplit_app_overview_v1.3_light.png&quot; alt=&quot;Kitty Overview - after&quot; title=&quot;Kitty Overview - after&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Kitty Overview - before&lt;/em&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Kitty Overview - after&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;!-- read-more --&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2025-12-09/kittysplit_app_overview_v1.3_dark.png&quot; alt=&quot;Kitty Overview - after (dark)&quot; title=&quot;Kitty Overview - after (dark)&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2025-12-09/kittysplit_app_manage_participants_v1.3_dark.png&quot; alt=&quot;Manage Participants - after (dark)&quot; title=&quot;Manage Participants - after (dark)&quot; width=&quot;320&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Kitty Overview - after (dark)&lt;/em&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Manage Participants - after (dark)&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;cat-avatars-for-each-participant&quot;&gt;Cat Avatars for Each Participant&lt;/h2&gt;

&lt;p&gt;With each Kitty every participant now gets a unique cat avatar. Yes, it fits the Kittysplit name, but there are practical reasons too.&lt;/p&gt;

&lt;p&gt;Visual identifiers help you scan information faster than text alone. The cats make it easier to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make the app feel less like a spreadsheet&lt;/li&gt;
  &lt;li&gt;Identify participants in larger groups&lt;/li&gt;
  &lt;li&gt;Spot who paid for what when scrolling through expenses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/2025-12-09/cat_avatars_sample.png&quot; alt=&quot;Cat Avatars (sample)&quot; title=&quot;Cat Avatars (sample)&quot; width=&quot;640&quot; /&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;em&gt;Cat Avatars (sample)&lt;/em&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;what-hasnt-changed&quot;&gt;What Hasn’t Changed&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;No registration required&lt;/li&gt;
  &lt;li&gt;Works on web, iOS, and Android&lt;/li&gt;
  &lt;li&gt;Smart debt calculations&lt;/li&gt;
  &lt;li&gt;Free for most features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;get-the-update&quot;&gt;Get the Update&lt;/h2&gt;

&lt;p&gt;Mobile users will get the update through the App Store or Google Play.&lt;/p&gt;

&lt;p&gt;Let us know what you think at info[at]kittysplit.com or through the feedback button.&lt;/p&gt;
</description>
        <pubDate>Tue, 09 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/improved-overview-and-cat-avatars/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/improved-overview-and-cat-avatars/</guid>
      </item>
    
      <item>
        <title>Kittysplit&apos;s RevenueCat Shipaton 2025 Story</title>
        <description>&lt;p&gt;We’re excited to share our story for the &lt;a href=&quot;https://www.shipaton.com&quot;&gt;RevenueCat Shipaton 2025&lt;/a&gt;! As part of this fun hackathon, we finally took the leap and shipped our long-awaited Android and iOS apps for Kittysplit.
The Shipaton challenged us to not just build something new, but to actually ship it and that’s exactly what we needed to push our mobile apps from “someday” to “today.” Below is the story we submitted for the hackathon, chronicling our journey from a simple side project born during a ski trip to launching our mobile apps.&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h1 id=&quot;kittysplit-easy-splitting-of-group-expenses&quot;&gt;Kittysplit: Easy splitting of group expenses&lt;/h1&gt;

&lt;h2 id=&quot;inspiration&quot;&gt;Inspiration&lt;/h2&gt;

&lt;p&gt;Kittysplit started as a side project born from a real problem we faced during a ski trip with friends years ago. We were struggling to split our expenses fairly and efficiently. Excel felt clunky and overcomplicated for what should be a simple task. When we couldn’t find an existing tool that was truly easy to use, we decided to build our own.&lt;/p&gt;

&lt;p&gt;What began as a web-only solution has grown organically over the years. We’ve continuously improved the platform based on constant feedback from our users, who have been incredibly helpful in shaping what Kittysplit has become today. As our user base grew, one request kept coming up:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“When will you have mobile apps?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We started working on the apps when we had time, but participating in this hackathon gave us the final push to finish and ship them to the app stores.&lt;/p&gt;

&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;/h2&gt;

&lt;p&gt;Kittysplit solves one problem really well: making group expense splitting effortless. Whether you’re on vacation with friends, sharing household costs with roommates, or organizing a group dinner, our app provides the simplest way to calculate who owes what to whom. No overcomplicated features, no confusing interfaces: just straightforward expense splitting that anyone can understand.&lt;/p&gt;

&lt;h2 id=&quot;how-we-built-it&quot;&gt;How we built it&lt;/h2&gt;

&lt;p&gt;The journey to mobile started with building an API to integrate with our existing web application. Our initial plan was ambitious: use Kotlin Multiplatform for shared business logic, networking, local storage, and calculations, and build two separate user interfaces: a Jetpack Compose UI for Android, and a native SwiftUI frontend for iOS.&lt;/p&gt;

&lt;p&gt;Since we already had Android development experience, we began with the Android app and built the multiplatform part and the Compose UI for Android. We took great care to keep our client side business logic fully separate from the UI implementation by using ViewModels with StateFlow in Kotlin multiplatform. This allowed us to reuse all business logic for our iOS app. For the multiplatform payment system integration, we used RevenueCat - it turns out that Kittysplit and RevenueCat are a purrr-fect combination!&lt;/p&gt;

&lt;p&gt;Then we started working on the SwiftUI client. Initially, it seemed quite straightforward and using our KMP ViewModels from SwiftUI code worked really well. However during the process, it turned out to be a lot more work than expected. We also realized that building our UI in Compose was much easier than in SwiftUI, and that Compose Multiplatform would save us significant development time and effort. This pivot allowed us to share not just the business logic, but also most of the UI code between platforms.&lt;/p&gt;

&lt;h2 id=&quot;challenges-we-ran-into&quot;&gt;Challenges we ran into&lt;/h2&gt;

&lt;p&gt;Most of our challenges were product-focused rather than technical. The biggest question was how to translate the simple, intuitive flows that work well on the web into mobile app experiences. We spent considerable time ensuring that the mobile version maintained the same simplicity that makes our web app successful. Keeping the product simple sounds easy, but the big challenge is hiding the technical and mathematical complexity from the user and make it all work intuitively.&lt;/p&gt;

&lt;p&gt;On the technical side, we took great care to keep our client side business logic separate from the UI implementation by using ViewModels in Kotlin multiplatform. The main challenge was realizing that building and maintaining a separate iOS frontend in SwiftUI besides our existing backend, web UI and the Android Compose app was too much work for us, as a small team of 3 people working part-time on this project. So we ditched our half finished SwiftUI app even though we already spent a lot of time on it. The switch to Compose Multiplatform was easier than expected and crucial to get the iOS app released soon after Android.&lt;/p&gt;

&lt;h2 id=&quot;accomplishments-that-were-proud-of&quot;&gt;Accomplishments that we’re proud of&lt;/h2&gt;

&lt;p&gt;The user response has been incredibly encouraging. Already during the beta phase, we received a lot of positive feedback from users. Many of our existing web users are actively switching to the mobile apps, which validates that our concept translates well to mobile. And new users already say that after trying several established competitor’s apps, they are happy to have found the Kittysplit app.&lt;/p&gt;

&lt;p&gt;What makes us most proud is that users immediately understand how to use the app. The simplicity and intuitive design we worked so hard to achieve on the web has successfully carried over to mobile. Users can start splitting expenses within seconds of opening the app.&lt;/p&gt;

&lt;h2 id=&quot;what-we-learned&quot;&gt;What we learned&lt;/h2&gt;

&lt;p&gt;The technical transition from Android Compose to Compose Multiplatform turned out to be surprisingly smooth. We learned a lot about both technologies throughout the process.&lt;/p&gt;

&lt;p&gt;Perhaps the most interesting learning curve was diving into the iOS ecosystem for the first time. We spent considerable time learning Swift and SwiftUI, only to eventually abandon that code when we switched to Compose Multiplatform. While it felt like wasted effort initially, understanding iOS development principles helped us make better decisions for the multiplatform approach.&lt;/p&gt;

&lt;p&gt;We also learned the intricacies of iOS app publishing which was a completely new world for us as primarily Android developers.&lt;/p&gt;

&lt;h2 id=&quot;whats-next-for-kittysplit&quot;&gt;What’s next for Kittysplit&lt;/h2&gt;

&lt;p&gt;Our immediate focus is on App Store Optimization to grow our mobile user base.&lt;/p&gt;

&lt;p&gt;We’re committed to continuing our user feedback driven development approach. Our feature roadmap includes several highly requested additions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Expense attachments (receipts, photos)&lt;/li&gt;
  &lt;li&gt;Expense categories for better organization&lt;/li&gt;
  &lt;li&gt;Push notifications to keep everyone updated&lt;/li&gt;
  &lt;li&gt;And much more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal remains the same: keep Kittysplit the easiest way to split group expenses, whether you’re using our web app or mobile apps.&lt;/p&gt;
</description>
        <pubDate>Tue, 30 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/revenuecat-shipaton-story/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/revenuecat-shipaton-story/</guid>
      </item>
    
      <item>
        <title>Collecting Reviews in a Compose Multiplatform App</title>
        <description>&lt;p&gt;Shipping a great app is only half the battle, earning high quality reviews is just as crucial for App Store Optimization (ASO). Ratings and reviews shape first impressions, drive conversion, and influence store ranking. For a product like Kittysplit, where trust and simplicity are everything, timely and authentic user feedback helps future users decide to install, and helps us steer the roadmap.&lt;/p&gt;

&lt;p&gt;But asking for reviews is a delicate craft. Prompt too early and you interrupt the flow, prompt too often and you annoy users, prompt at the wrong moment and you get low‑quality or frustrated feedback. In this post, we’ll share how we integrated Google Play’s &lt;a href=&quot;https://developer.android.com/guide/playcore/in-app-review/kotlin-java&quot;&gt;ReviewManager&lt;/a&gt; and Apple’s &lt;a href=&quot;https://developer.apple.com/documentation/storekit/skstorereviewcontroller/&quot;&gt;SKStoreReviewController&lt;/a&gt; into our Compose Multiplatform app, and how we built a cross‑platform strategy to decide when to ask. We’ll cover the technical glue, the platform nuances, and the heuristics we use to surface the prompt at moments when users are most likely to feel satisfied, not interrupted.&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;We’ll start with the implementation. We define a shared &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewService&lt;/code&gt; API in common code and provide platform-specific implementations that wrap Android’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewManager&lt;/code&gt; and iOS’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKStoreReviewController&lt;/code&gt;. Using Kotlin Mutliplatform’s expect/actual mechanism in combination with &lt;a href=&quot;https://insert-koin.io&quot;&gt;Koin&lt;/a&gt;, we’re providing the correct implementation for each platform. This keeps the call site in our Compose Multiplatform UI identical across platforms, while letting each target handle its own prompt presentation in a native, compliant way.&lt;/p&gt;

&lt;p&gt;We’re using a freshly created Compose Multiplatform project as the base for this post. You can find a little more detailed description of how to create a new CMP project in our previous blog post &lt;a href=&quot;/using-firebase-analytics-in-cmp&quot;&gt;here&lt;/a&gt;. You can find all the code on &lt;a href=&quot;https://github.com/Kittysplit/ReviewServiceCMP&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At first, we need to add a few dependencies to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libs.versions.toml&lt;/code&gt;. If you don’t know what that is, you can read up on it &lt;a href=&quot;https://developer.android.com/build/migrate-to-catalogs&quot;&gt;here&lt;/a&gt;. It’s a relatively new way of managing dependencies in gradle.&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... other verions&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;android-play-review&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.0.2&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.1.1&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;kotlinx-coroutines&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.10.2&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libraries&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... other libraries&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;android-play-review&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.android.play:review&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android-play-review&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;android-play-review-ktx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.android.play:review-ktx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;android-play-review&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;koin-compose&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;io.insert-koin:koin-compose&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;koin&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;koin-core&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;io.insert-koin:koin-core&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;koin&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;kotlinx-coroutines-core&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kotlinx-coroutines&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;kotlinx-coroutines-play-services&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;org.jetbrains.kotlinx:kotlinx-coroutines-play-services&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kotlinx-coroutines&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./gradle/libs.versions.toml&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, we’re going to declare these dependencies in our compose app’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle.kts&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;kotlin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ... other settings&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;sourceSets&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;androidMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ... other android dependencies&lt;/span&gt;

            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;android&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;review&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;android&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;review&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ktx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kotlinx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;commonMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ... other common dependencies&lt;/span&gt;

            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kotlinx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/build.gradle.kts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we need to define an interface in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commonMain&lt;/code&gt; that we’ll implement later in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;androidMain&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt; respectively:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EmptyActivityContainer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Requested&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;data class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RequestError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;requestInAppReview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;activityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EmptyActivityContainer&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/reviewservicecmp/ReviewService.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because Apple’s SKStoreReviewController and Google Play’s ReviewManager don’t expose whether a dialog was actually shown or acted upon, our API only reports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Requested&lt;/code&gt; or an error. This keeps our contract honest and portable across both stores’ opaque review flows. Don’t mind the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityContainer&lt;/code&gt; interface and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EmptyActivityContainer&lt;/code&gt; object, we’re going to explain that soon.&lt;/p&gt;

&lt;h2 id=&quot;android&quot;&gt;Android&lt;/h2&gt;

&lt;p&gt;Let’s look at the implementation for Android:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;android.app.Activity&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;android.content.Context&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.google.android.play.core.review.ReviewException&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.google.android.play.core.review.ReviewManagerFactory&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kotlinx.coroutines.tasks.await&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;data class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AndroidContextHolder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AndroidContextHolder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AndroidReviewService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;reviewManager&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewManagerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;requestInAppReview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;activityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;reviewInfo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;requestReviewFlow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;reviewManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launchReviewFlow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reviewInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Requested&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RequestError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;errorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/androidMain/kotlin/com/kittysplit/reviewservicecmp/AndroidReviewService.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We implemented the Android side of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewService&lt;/code&gt; using Play Core’s in‑app review flow. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AndroidReviewService&lt;/code&gt; creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewManager&lt;/code&gt;, requests a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewInfo&lt;/code&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestReviewFlow().await()&lt;/code&gt;, and then launches the prompt with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reviewManager.launchReviewFlow(...).await()&lt;/code&gt;. Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchReviewFlow&lt;/code&gt; requires an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt;, not a generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt;, we created a small container object to be able to hand over the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; in the Android implementation. For iOS, we’re going to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EmptyActivityContainer&lt;/code&gt;, since we don’t need to pass anything there.&lt;/p&gt;

&lt;p&gt;If something goes wrong on Android, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewManager&lt;/code&gt; might throw a &lt;a href=&quot;https://developer.android.com/reference/com/google/android/play/core/review/ReviewException&quot;&gt;ReviewException&lt;/a&gt;. In case we encounter it, we’re extracting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errorCode&lt;/code&gt; and return it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewResult.RequestError&lt;/code&gt;. This allows you to handle these errors separately from the generic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewResult.Failed&lt;/code&gt;. For possible values of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;errorCode&lt;/code&gt; see the documentation &lt;a href=&quot;https://developer.android.com/reference/com/google/android/play/core/review/model/ReviewErrorCode&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ios&quot;&gt;iOS&lt;/h2&gt;

&lt;p&gt;Next, we’re going to implement the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppleReviewService&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;platform.StoreKit.SKStoreReviewController&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;platform.UIKit.UIApplication&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;platform.UIKit.UISceneActivationStateForegroundActive&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;platform.UIKit.UIWindowScene&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppleReviewService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;requestInAppReview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActivityContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;scene&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UIApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sharedApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connectedScenes&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filterIsInstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UIWindowScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;firstOrNull&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activationState&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UISceneActivationStateForegroundActive&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scene&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;SKStoreReviewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;requestReviewInScene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scene&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;SKStoreReviewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;requestReview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Requested&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ReviewResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/iosMain/kotlin/com/kittysplit/reviewservicecmp/AppleReviewService.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We implemented the iOS side with StoreKit’s in‑app review API. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppleReviewService&lt;/code&gt; looks up the currently active &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIWindowScene&lt;/code&gt; by scanning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UIApplication.sharedApplication.connectedScenes&lt;/code&gt; and picking the one in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UISceneActivationStateForegroundActive&lt;/code&gt; state. If an active scene exists, we call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKStoreReviewController.requestReviewInScene(scene)&lt;/code&gt;. Otherwise we fall back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKStoreReviewController.requestReview()&lt;/code&gt;. Both calls are “fire‑and‑forget” (no success callback), so we return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewResult.Requested&lt;/code&gt; after invoking them. Any exception during lookup or invocation maps to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewResult.Failed&lt;/code&gt;. This approach ensures the prompt is tied to the foreground scene in multi‑window setups while remaining compatible with older, scene‑agnostic code paths.&lt;/p&gt;

&lt;h2 id=&quot;koin&quot;&gt;Koin&lt;/h2&gt;

&lt;p&gt;That’s it. All we need to do now is wire it all together to be able to use it in our common app code. We’re going to use &lt;a href=&quot;https://insert-koin.io&quot;&gt;Koin&lt;/a&gt; for that, but you can go with any other DI framework.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reviewModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/reviewservicecmp/ReviewModule.kt&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.dsl.singleOf&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.dsl.module&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reviewModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;singleOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AndroidReviewService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/androidMain/kotlin/com/kittysplit/reviewservicecmp/ReviewModule.android.kt&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.dsl.singleOf&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.dsl.module&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reviewModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;singleOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AppleReviewService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/iosMain/kotlin/com/kittysplit/reviewservicecmp/ReviewModule.ios.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’re not going in detail here, but it should be straight forward. We’re defining a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reviewModule()&lt;/code&gt; as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect&lt;/code&gt; function and provide the platform-specific implementations in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actual&lt;/code&gt; counterparts.&lt;/p&gt;

&lt;p&gt;With both implementations in place, you can inject &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewService&lt;/code&gt; into any ViewModel and trigger review requests at the right moments in your user flows. Pick thoughtful triggers, wire them to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestInAppReview&lt;/code&gt;, and you’re ready to ask for reviews without disrupting your users.&lt;/p&gt;

&lt;h2 id=&quot;review-triggers&quot;&gt;Review triggers&lt;/h2&gt;

&lt;p&gt;We gate review prompts behind lightweight heuristics so they appear at moments of success, not interruption. We track when the app was first opened, total opens, when we last requested a review, and how many expenses a user has added. We use two trigger events: when a user adds an expense and when they settle up. We prioritized the settle-up event because it aligns with a clear success moment. Our heuristics are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Last review request ≥ x days ago&lt;/li&gt;
  &lt;li&gt;First app open ≥ x days ago&lt;/li&gt;
  &lt;li&gt;App opened ≥ x times&lt;/li&gt;
  &lt;li&gt;Current Kitty has ≥ x expenses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We apply these on both platforms, but we’re more conservative on iOS, prompting less frequently and only after stronger engagement because the app is only three weeks old, and because Apple only displays the review prompt maximum &lt;a href=&quot;https://developer.apple.com/documentation/storekit/requesting-app-store-reviews&quot;&gt;3 times a year&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Collecting reviews is both a technical integration and a product judgment call. With a shared &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewService&lt;/code&gt; and platform-native implementations for Android’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReviewManager&lt;/code&gt; and iOS’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKStoreReviewController&lt;/code&gt;, you now have a clean way to prompt your users for reviews from your common code. Pair that with thoughtful heuristics that favor genuine success moments, and you’ll nudge happy users to share feedback without breaking their flow, ultimately improving ASO, conversion, and the product itself.&lt;/p&gt;

&lt;p&gt;If you try this approach in your own Compose Multiplatform app, we’d love to hear what heuristics work best for you. Feels free to contact us on social networks (&lt;a href=&quot;https://x.com/kittysplit&quot;&gt;X&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/kittysplit.bsky.social&quot;&gt;Bluesky&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/kittysplit.app/&quot;&gt;Instagram&lt;/a&gt;) or write an email.&lt;/p&gt;
</description>
        <pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/collect_reviews_in_a_compose_multiplatform_app/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/collect_reviews_in_a_compose_multiplatform_app/</guid>
      </item>
    
      <item>
        <title>Firebase Analytics in Compose Multiplatform without Cocoapods</title>
        <description>&lt;p&gt;At Kittysplit, we’ve always aimed to make splitting group expenses as effortless as possible. For years, Kittysplit was a web first product. As our users took Kittysplit on trips, into shared apartments, and to events, it became clear that we needed &lt;a href=&quot;/mobile-app-release/&quot;&gt;mobile apps&lt;/a&gt; on both Android and iOS.&lt;/p&gt;

&lt;p&gt;In this post, we’ll provide a hands-on guide how we wired Firebase Analytics into our Compose Multiplatform app on both Android and iOS without using Cocoapods (which is going to &lt;a href=&quot;https://blog.cocoapods.org/CocoaPods-Specs-Repo/&quot;&gt;retire&lt;/a&gt; soon).&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;from-swiftui-to-compose-multiplatform&quot;&gt;From SwiftUI to Compose Multiplatform&lt;/h2&gt;

&lt;p&gt;We embraced Kotlin Multiplatform to share business logic across platforms, initially pairing it with a native Android app and a SwiftUI app for iOS, each with its own &lt;a href=&quot;https://firebase.google.com/docs/analytics&quot;&gt;Firebase Analytics&lt;/a&gt; integration.&lt;/p&gt;

&lt;p&gt;But when we decided to &lt;a href=&quot;/building-kittysplit-apps-in-cmp/&quot;&gt;fully commit to Compose Multiplatform&lt;/a&gt; and retire our SwiftUI app, we hit a wall: there’s no out of the box way to use Firebase Analytics in a Compose Multiplatform project. Also, Cocoapods is going to &lt;a href=&quot;https://blog.cocoapods.org/CocoaPods-Specs-Repo/&quot;&gt;retire&lt;/a&gt; soon. Instead, we’ll use the Swift Package Manager dependencies directly with the wonderful &lt;a href=&quot;https://github.com/frankois944/spm4Kmp&quot;&gt;spm4Kmp&lt;/a&gt; plugin made by &lt;a href=&quot;https://github.com/frankois944&quot;&gt;François&lt;/a&gt;. We’re also going to use &lt;a href=&quot;https://insert-koin.io&quot;&gt;Koin&lt;/a&gt; to wire it all together.&lt;/p&gt;

&lt;h2 id=&quot;project-setup&quot;&gt;Project setup&lt;/h2&gt;

&lt;p&gt;We’re going to add Firebase Analytics to a newly created Compose Multiplatform project, which we’re creating using the new project wizard in Android Studio. If you want to follow along, you can either check out the &lt;a href=&quot;https://github.com/Kittysplit/FirebaseAnalyticsCMP&quot;&gt;project code&lt;/a&gt; from GitHub or create a new project yourself.&lt;/p&gt;

&lt;p&gt;To create a new CMP project in Android Studio, make sure you have the &lt;a href=&quot;https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform&quot;&gt;Kotlin Multiplatform plugin&lt;/a&gt; installed. You can then click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File -&amp;gt; New -&amp;gt; New Project...&lt;/code&gt;. Under “Phone and Tablet” select “Kotlin Multiplatform”, enter a name and package for your app and click “Next”. On the next screen, make sure you have the Android and iOS boxes ticked and select “Share UI” as UI implementation. Click “Finish” to create the new project.&lt;/p&gt;

&lt;h2 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h2&gt;

&lt;p&gt;Our first step is to add the needed dependencies. To do this, we’re going to use the &lt;a href=&quot;https://docs.gradle.org/current/userguide/version_catalogs.html&quot;&gt;gradle version catalog&lt;/a&gt;. Locate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libs.versions.toml&lt;/code&gt; file and add the following lines:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... other dependencies&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.1.1&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spm4kmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0.0-Beta04&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;firebase-analytics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;22.5.0&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;google-services&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.4.3&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# spm&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spm-firebase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;12.0.0&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libraries&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... other libraries&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;koin-compose&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;io.insert-koin:koin-compose&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;koin&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;koin-core&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;io.insert-koin:koin-core&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;koin&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;firebase-analytics-ktx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.firebase:firebase-analytics-ktx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;firebase-analytics&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... other plugins&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;spm4kmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;io.github.frankois944.spmForKmp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spm4kmp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;google-services&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.gms.google-services&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;google-services&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./gradle/libs.versions.toml&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can now sync your project with the gradle files to pull in the dependencies. With this out of the way, we’re going to declare the dependencies in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle.kts&lt;/code&gt; files. We’ll start with the root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle.kts&lt;/code&gt; file. Add this line:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// other plugins&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spm4kmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;google&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./build.gradle.kts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This just tells gradle to resolve the plugin, but not apply it. This is useful if you have a multi-module gradle project and only want to apply the plugin in a single submodule.&lt;/p&gt;

&lt;p&gt;Now we’re going to set up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle.build.kts&lt;/code&gt; of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composeApp&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// other plugins&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spm4kmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;google&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;nf&quot;&gt;kotlin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;iosArm64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;iosSimulatorArm64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;forEach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iosTarget&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;iosTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binaries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;framework&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;baseName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ComposeApp&quot;&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;binaryOption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bundleId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;com.kittysplit.firebase_analytics_cmp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;isStatic&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// This is the important part:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;iosTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compilations&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getting&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;cinterops&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nativeIosShared&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;sourceSets&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;named&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lowercase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ios&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configureEach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;languageSettings&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;optIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;kotlinx.cinterop.ExperimentalForeignApi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;androidMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firebase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ktx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;commonMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

            &lt;span class=&quot;nf&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;koin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;swiftPackageConfig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;nativeIosShared&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;minIos&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;16.0&quot;&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;dependency&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;remotePackageVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://github.com/firebase/firebase-ios-sdk.git&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FirebaseAnalytics&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exportToKotlin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;FirebaseCore&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exportToKotlin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;packageName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;firebase-ios-sdk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;libs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firebase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/build.gradle.kts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s all dependency juggling. We added koin as “regular” multiplatform dependency of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commonMain&lt;/code&gt;. For the Android implementation of Firebase Analytics, we added the Android library to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;androidMain&lt;/code&gt;. To be able to use the iOS Firebase Analytics in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt; implementation, we used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spm4Kmp&lt;/code&gt; to declare the dependency. Notice the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exportToKotlin = true&lt;/code&gt;, this tells &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spm4Kmp&lt;/code&gt; to export the dependency to kotlin, which means we can use it directly in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt; code.&lt;/p&gt;

&lt;p&gt;When you sync the project again, you’ll notice a new folder: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./composeApp/exportedNativeIosShared&lt;/code&gt;. This is the generated swift package which we’re going to add as a dependency to the iOS app next. Open XCode and navigate to the project dependencies:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-23-using-firebase-analytics-in-cmp--xcode-add-dependency.png&quot; alt=&quot;XCode&quot; /&gt;
&lt;em&gt;XCode: iosApp -&amp;gt; PROJECT (iosApp) -&amp;gt; Package Dependencies&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Click on the little “+” sign and then on “Add Local…”. Now select the aforementioned folder and click “Add package”. Once done, you’ll see all dependencies of Firebase Analytics and Firebase itself in your package dependencies on the lower left of your XCode window.&lt;/p&gt;

&lt;h2 id=&quot;firebase-setup&quot;&gt;Firebase setup&lt;/h2&gt;

&lt;p&gt;To function properly, you need to register your Android and iOS Apps with Firebase. We’re not going to explain this in detail in this blogpost. Essentially, you need to provide a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GoogleService-Info.plist&lt;/code&gt; file on iOS and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;google-services.json&lt;/code&gt; file on Android. You can find an in depth guide for Android &lt;a href=&quot;https://firebase.google.com/docs/android/setup&quot;&gt;here&lt;/a&gt;. The guide for iOS is located &lt;a href=&quot;https://firebase.google.com/docs/ios/setup&quot;&gt;here&lt;/a&gt;. Please note that you only need to follow the steps to obtain the aforementioned files, all other parts are covered in this blog post.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;This completes the boring part. We’re going to look at the implementation next. Our goal is to create a simple interface that allows us to track events to Firebase Analytics from view models, but also from Composables.&lt;/p&gt;

&lt;p&gt;We’re starting by creating a simple interface inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commonMain&lt;/code&gt;, which we’re going to implement in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;androidMain&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/Analytics.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the Android implementation, we’re going to use the kotlin Firebase SDK in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;androidMain&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.core.os.bundleOf&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.google.firebase.analytics.FirebaseAnalytics&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.google.firebase.analytics.ktx.analytics&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.google.firebase.ktx.Firebase&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AndroidAnalytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;firebaseAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FirebaseAnalytics&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;firebaseAnalytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Firebase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toTypedArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bundleOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;firebaseAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/androidMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/AndroidAnalytics.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the implementation is straight forward. All we have to do is to convert our parameter map to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bundle&lt;/code&gt;, which is often used in Android to pass data around.&lt;/p&gt;

&lt;p&gt;On to the iOS implementation. We’re going to use the native iOS Firebase Analytics SDK in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iosMain&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FirebaseAnalytics.FIRAnalytics&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FirebaseCore.FIRApp&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppleAnalytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;firAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FIRAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Companion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FIRAnalytics&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;FIRApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;firAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logEventWithName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/iosMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/AppleAnalytics.kt&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;dependency-injection&quot;&gt;Dependency injection&lt;/h2&gt;

&lt;p&gt;All that is left to do now is to wire it all up using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;koin&lt;/code&gt;. We’ll have to create a module that provides our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppleAnalytics&lt;/code&gt; if we’re running on iOS and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AndroidAnalytics&lt;/code&gt; implementation if we’re running on Android. That’s where kotlin’s easy to use [expect/actual declarations] https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-expect-actual.html come in.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;commonMain&lt;/code&gt;, we’re only declaring our module as an expect function, which is comparable to an interface where the implementation are provided per platform:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;analyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/AnalyticsModule.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, we’re providing the implementations for Android:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.dsl.module&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;analyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AndroidAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/androidMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/AnalyticsModule.android.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And iOS:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.core.module.Module&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.dsl.module&lt;/span&gt;


&lt;span class=&quot;n&quot;&gt;actual&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;analyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppleAnalytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/iosMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/AnalyticsModule.ios.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s important to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; on our implementations, to make sure they’re ready to go when they’re injected. Now we have to make sure that koin knows about the module we’ve just created. We’re going to do that by starting koin when our application starts. On Android, we can easily do that in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainActivity&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;android.os.Bundle&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.activity.ComponentActivity&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.activity.compose.setContent&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.activity.enableEdgeToEdge&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.compose.runtime.Composable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.compose.ui.tooling.preview.Preview&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics.analyticsModule&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.compose.KoinApplication&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MainActivity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ComponentActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;savedInstanceState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Bundle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;enableEdgeToEdge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;savedInstanceState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;setContent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;KoinApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;analyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/androidMain/kotlin/com/kittysplit/firebase_analytics_cmp/MainActivity.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make it work on iOS, we need to start the koin application in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MainViewController&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;androidx.compose.ui.window.ComposeUIViewController&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics.analyticsModule&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.compose.KoinApplication&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MainViewController&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ComposeUIViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KoinApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;analyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/iosMain/kotlin/com/kittysplit/firebase_analytics_cmp/MainViewController.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we’re ready to inject our analytics implementation anywhere we need it. To test it, we’re going to use it to track the tap on the “Click me!” button from the example:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ... imports&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics.Analytics&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.compose.koinInject&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Composable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;koinInject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;MaterialTheme&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;showContent&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remember&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mutableStateOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;modifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Modifier&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MaterialTheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorScheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primaryContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;safeContentPadding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fillMaxSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;horizontalAlignment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CenterHorizontally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;onClick&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button_clicked&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;showContent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showContent&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Click me!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;AnimatedVisibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;showContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;greeting&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;remember&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;modifier&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Modifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fillMaxWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;horizontalAlignment&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Alignment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CenterHorizontally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;painterResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;drawable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compose_multiplatform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Compose: $greeting&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/firebase_analytics_cmp/App.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s it! You’re now ready to track events from anywhere in your Android Compose app. There’s one little thing we need to fix. You might have noticed that your preview is not showing any longer. That’s because koin is not initialized. Luckily, there’s a fix for that: we’re going to provide a fake instance of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Analytics&lt;/code&gt; for previews:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.kittysplit.firebase_analytics_cmp.analytics&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.koin.dsl.module&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;noopAnalyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Analytics&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;./composeApp/src/commonMain/kotlin/com/kittysplit/firebase_analytics_cmp/analytics/NoopAnalyticsModule.kt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now all we have to do is wrap our previews with a little helper:&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Preview&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Composable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AppPreview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;KoinApplicationPreview&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;modules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;noopAnalyticsModule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you should see the preview again.&lt;/p&gt;

&lt;p&gt;We hope this little guide helps you in your multiplatform endeavours. We had a lot of fun implementing the Kittysplit apps in Compose Multiplatform.&lt;/p&gt;
</description>
        <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/using-firebase-analytics-in-cmp/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/using-firebase-analytics-in-cmp/</guid>
      </item>
    
      <item>
        <title>Building Kittysplit&apos;s Mobile Apps in Compose Multiplatform</title>
        <description>&lt;p&gt;After 13 years as a web only application, Kittysplit has finally made the leap to mobile. We initially planned to build separate Android and iOS apps using Kotlin Multiplatform for shared business logic, but Compose Multiplatform’s recent stability changed everything. Here’s how we went from web only to native mobile apps.&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;from-web-only-to-mobile-a-13-year-journey&quot;&gt;From Web only to Mobile: A 13 Year Journey&lt;/h2&gt;

&lt;p&gt;Kittysplit started as a web application back in 2012. For over a decade, we maintained it exclusively as a webapp. The web platform allowed us to provide universal accessibility. Anyone could create or join a Kitty simply by opening a link in their browser, regardless of their device type. This cross platform compatibility is crucial for group expense sharing, where participants often use different devices and operating systems.&lt;/p&gt;

&lt;h2 id=&quot;growing-demand-for-native-apps&quot;&gt;Growing Demand for Native Apps&lt;/h2&gt;

&lt;p&gt;However, user requests for dedicated mobile apps grew year after year. We felt that we needed to address this demand, so we looked at the available options for the implementation. We evaluated &lt;a href=&quot;https://reactnative.dev&quot;&gt;React Native&lt;/a&gt;, &lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt;, &lt;a href=&quot;https://kotlinlang.org/docs/multiplatform.html&quot;&gt;Kotlin Multiplatform&lt;/a&gt;, and &lt;a href=&quot;https://www.jetbrains.com/compose-multiplatform/&quot;&gt;Compose Multiplatform&lt;/a&gt; to determine the best approach for our small development team.&lt;/p&gt;

&lt;p&gt;Because we already had experience with Android development, we chose Kotlin Multiplatform as our foundation. Our initial plan was straightforward: develop the Android app first using &lt;a href=&quot;https://developer.android.com/compose&quot;&gt;Android Compose&lt;/a&gt;, while implementing all business logic in a shared module in Kotlin Multiplatform. This would allow us to share the core functionality across platforms: networking, business logic, calculations and local storage. We planned to build the iOS app using SwiftUI after completing the Android app, leveraging the already written shared business logic.&lt;/p&gt;

&lt;h2 id=&quot;the-power-of-kotlin-multiplatform&quot;&gt;The Power of Kotlin Multiplatform&lt;/h2&gt;

&lt;p&gt;Kotlin Multiplatform proved to be an excellent choice for our architecture. We implemented all networking, local storage, and client-side calculations in the shared multiplatform code. Additionally, we moved all our ViewModels into the shared codebase, ensuring consistent business logic and state management across both platforms.&lt;/p&gt;

&lt;p&gt;The implementation was straightforward: Kotlin provided almost everything we needed out of the box. For local data storage, we used &lt;a href=&quot;https://sqldelight.github.io/sqldelight/&quot;&gt;SQLDelight&lt;/a&gt;, which allowed us to write type-safe SQL queries. We integrated the &lt;a href=&quot;https://www.revenuecat.com/docs/getting-started/installation/kotlin-multiplatform&quot;&gt;RevenueCat Purchases multiplatform SDK&lt;/a&gt; for handling Super Kitty upgrades. To enhance the Swift interoperability for our planned iOS development, we used &lt;a href=&quot;https://skie.touchlab.co&quot;&gt;SKIE&lt;/a&gt; from TouchLab. For dependency injection, we chose &lt;a href=&quot;https://insert-koin.io&quot;&gt;Koin&lt;/a&gt;, which worked seamlessly across our multiplatform setup. This foundation gave us confidence that we could maintain a single source of truth for our core functionality while building native experiences on each platform.&lt;/p&gt;

&lt;h2 id=&quot;compose-multiplatform-goes-stable&quot;&gt;Compose Multiplatform Goes Stable&lt;/h2&gt;

&lt;p&gt;As it’s often the case in software development, the SwiftUI frontend turned out to be more work than we initially anticipated 🙈. We had been following Compose Multiplatform’s development for some time. Given the challenges we were facing with SwiftUI, we decided to experiment with it for a few days. Compose Multiplatform for iOS had recently reached stable status in May 2025.&lt;/p&gt;

&lt;p&gt;We managed to migrate our app to Compose Multiplatform within just a few days, which was much faster than we expected. The resulting app ran smoothly on our iOS devices and felt really well.&lt;/p&gt;

&lt;p&gt;This led us to ditch the native SwiftUI frontend entirely and go with Compose Multiplatform instead. It would save us months of work, both now and in the future. With only one codebase to maintain, we can spend our time improving the app and adding new features rather than implementing everything twice.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-18-building-kittysplit-apps-in-cmp--kittysplit-ios-fte-screenshot.png&quot; alt=&quot;Kittysplit first time on iOS&quot; height=&quot;600&amp;quot;, style=&amp;quot;float: left&quot; /&gt;
&lt;img src=&quot;/images/2025-09-18-building-kittysplit-apps-in-cmp--kittysplit-ios-add-entry-screenshot.png&quot; alt=&quot;Kittysplit add entry on iOS&quot; height=&quot;600&amp;quot;, style=&amp;quot;float: right&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kittysplit on iOS&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-migration-process-from-android-compose-to-compose-multiplatform&quot;&gt;The Migration Process: From Android Compose to Compose Multiplatform&lt;/h2&gt;

&lt;p&gt;The actual migration process was surprisingly straightforward. We started by importing the new Compose Multiplatform dependencies and migrating to multiplatform resources, which was mostly just copying a few files to the new structure. The main code changes involved removing usages of the Android Context throughout our codebase.&lt;/p&gt;

&lt;p&gt;Fortunately, we had already been using multiplatform libraries from Sentry and RevenueCat, so those integrations continued to work without any changes. For Firebase Analytics, we used the expect/actual mechanism to provide platform-specific implementations. We also needed to make our deep links work on both platforms, but the patterns were similar enough that it didn’t require major restructuring.&lt;/p&gt;

&lt;p&gt;Most of our existing Compose UI code worked with minimal adjustments, and the shared ViewModels we had already implemented in Kotlin Multiplatform integrated seamlessly. Within just a few days, we had a fully functional iOS app that felt genuinely good.&lt;/p&gt;

&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward&lt;/h2&gt;

&lt;p&gt;We’re excited about the future of Kittysplit’s mobile apps with this technology stack. Kotlin Multiplatform and Compose Multiplatform have given us the perfect balance of development efficiency and native performance. With a single codebase to maintain, we can focus on what matters most: building features that make splitting expenses even easier for our users. We’re already working on several improvements and new features that would have taken us much longer to implement across separate platforms. The mobile app journey has finally found its ideal technical foundation, and we couldn’t be more excited about where it’s headed.&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/building-kittysplit-apps-in-cmp/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/building-kittysplit-apps-in-cmp/</guid>
      </item>
    
      <item>
        <title>Kittysplit goes mobile: Introducing our Android and iOS apps</title>
        <description>&lt;p&gt;After more than a decade as a web-only platform, Kittysplit is finally available as native mobile apps for both Android and iOS! Since our launch in 2012, we’ve been helping friends, roommates, and travel groups split expenses fairly and hassle-free through our web application.&lt;/p&gt;

&lt;h2 id=&quot;why-mobile-apps-why-now&quot;&gt;Why Mobile Apps? Why Now?&lt;/h2&gt;

&lt;p&gt;We’ve always believed in creating the most accessible expense-splitting solution possible. For years, our web app has served users well, allowing anyone with a browser to easily manage their group expenses. However, we’ve listened to your feedback and recognized that many of you prefer the convenience of a dedicated mobile app experience.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Instant access&lt;/strong&gt;: to your group expenses without opening a browser&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Smoother user experience&lt;/strong&gt;: optimized specifically for mobile devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-10-mobile-app-release--app-kitty-overview.png&quot; alt=&quot;Kitty Overview on iOS&quot; title=&quot;Kitty Overview on iOS&quot; width=&quot;320&quot; /&gt; &lt;img src=&quot;/images/2025-09-10-mobile-app-release--app-entries.png&quot; alt=&quot;Kitty Entries on iOS&quot; title=&quot;Kitty Entries on iOS&quot; width=&quot;320&quot; /&gt;&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;still-the-same-kittysplit-you-love&quot;&gt;Still the Same Kittysplit You Love&lt;/h2&gt;

&lt;p&gt;While we’re excited about our new mobile presence, rest assured that the Kittysplit experience remains unchanged. We’re still committed to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt; No complicated features you don’t need.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No registration required:&lt;/strong&gt; Create Kitties without creating an account.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fair splitting:&lt;/strong&gt; Multiple options for dividing expenses equitably.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Free basic usage:&lt;/strong&gt; Core functionality remains free to use.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No app required:&lt;/strong&gt; The mobile apps are our offering to a smoother mobile user experience, but Kittysplit on web will live on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-10-mobile-app-release--kitties-on-vacation.svg&quot; alt=&quot;Kitties on vacation&quot; title=&quot;Kitties on vacation&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;our-commitment-to-the-web&quot;&gt;Our Commitment to the Web&lt;/h2&gt;

&lt;p&gt;We believe in giving our users choices. As happy as we are to finally offer our users mobile apps, we want to emphasize that &lt;strong&gt;we remain fully committed to our web platform&lt;/strong&gt;. We understand that not everyone wants to download another app, and the flexibility of a web solution remains incredibly valuable for many of our users.&lt;/p&gt;

&lt;p&gt;The web version of Kittysplit will continue to receive the same attention, updates, and new features as our mobile apps.&lt;/p&gt;

&lt;h2 id=&quot;download-today&quot;&gt;Download Today!&lt;/h2&gt;

&lt;p&gt;Our new mobile apps are available now on the Apple App Store and Google Play Store. Whether you’re a long-time Kittysplit user or just discovering us, we invite you to try our mobile experience while continuing to enjoy the flexibility of our web platform.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/app/apple-store/id6742316700?pt=127648574&amp;amp;ct=ks_blog_post_app_release&amp;amp;mt=8&quot;&gt;&lt;img src=&quot;/images/badge_apple_app_store.svg&quot; alt=&quot;Download on the Apple App Store&quot; width=&quot;179&quot; /&gt;&lt;/a&gt; &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.kittysplit&amp;amp;referrer=utm_campaign%3Dks_blog_link%26utm_medium%3Dks_blog%26utm_source%3Dks_blog_post_app_release&quot;&gt;&lt;img src=&quot;/images/badge_google_play_store.png&quot; alt=&quot;Get it on Google Play&quot; width=&quot;200&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Or continue using our web app at &lt;a href=&quot;https://www.kittysplit.com&quot;&gt;kittysplit.com&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;This mobile launch is just the beginning. We’re already working on improvements and upcoming features based on all your feedback.&lt;/p&gt;

&lt;p&gt;We’d love to hear your feedback on the new apps and any suggestions for future improvements! As always, we remain committed to making expense splitting as simple and stress-free as possible — now on even more platforms.&lt;/p&gt;
</description>
        <pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/mobile-app-release/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/mobile-app-release/</guid>
      </item>
    
      <item>
        <title>Adsense is broken</title>
        <description>&lt;p&gt;We already had trouble with scam ads on Adsense &lt;a href=&quot;/adsense-bad-actors&quot;&gt;several years ago&lt;/a&gt;. While such rather obvious scams like the fake virus warning from 2018 seem to have disappeared, other scam ads have shown up.&lt;/p&gt;

&lt;p&gt;Last year, a user contacted us asking to cancel their subscription - but Kittysplit doesn’t offer any subscriptions. We soon figured that the user has been scammed by a fraudulent advertisement displayed while using Kittysplit on a phone.&lt;/p&gt;

&lt;!-- read-more --&gt;

&lt;h2 id=&quot;how-the-user-was-tricked&quot;&gt;How the user was tricked&lt;/h2&gt;

&lt;p&gt;The scam is simple - the ad shows a huge, green &lt;strong&gt;START&lt;/strong&gt; button, pretending to be part of Kittysplit. But the button takes the user to a third party site that asks for the user’s credit card details. It pretends the cost is for a service of Kittysplit (eg settling up with your friends, cost for using the site). Initially it’s free or almost free, but starts charging a monthly subscription costing anywhere between 20 and 60 €/$/£ for a service the user never wanted.&lt;/p&gt;

&lt;p&gt;Here’s a screenshot of a deceptive ad:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024-03-adsense-scam.jpg&quot; alt=&quot;Misleading ad example&quot; title=&quot;Misleading ad example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As we became aware of the problem, we quickly took action and started blocking such ads in the Adsense ad review center. This seemed to solve the issue for a while, but the ads kept coming back in different variations - always a big and bright button saying &lt;strong&gt;START&lt;/strong&gt;, &lt;strong&gt;CONTINUE&lt;/strong&gt;, &lt;strong&gt;NEXT&lt;/strong&gt;, &lt;strong&gt;DOWNLOAD&lt;/strong&gt; or similar.&lt;/p&gt;

&lt;p&gt;A few months ago, another user contacted us who had also been scammed by these ads. So we blocked the misleading ads again and again, passing many hours blocking thousands of ads and hundreds of advertiser accounts, hoping that this would solve the problem. But it just didn’t stop, and no matter how many times we blocked these ads, they came back after a few days.&lt;/p&gt;

&lt;h2 id=&quot;why-we-decided-to-stop-serving-ads-in-order-to-protect-our-users&quot;&gt;Why we decided to stop serving ads in order to protect our users&lt;/h2&gt;

&lt;p&gt;But it turns out it’s impossible to catch all bad ads, and recently a third user got scammed. So we decided it’s too much and &lt;a href=&quot;https://x.com/kittysplit/status/1754481485736816847&quot;&gt;turned off all ads last month&lt;/a&gt;. This means Kittysplit is now ads free (woo-hoo) until we find another ad solution, or Adsense fixes the problem. We know nobody likes ads anyway, but we rely on ads revenue to keep free Kitties free for you.&lt;/p&gt;

&lt;p&gt;While this costs us money in lost ads revenue, it’s more important to us to protect our users from being scammed by bad advertisers.&lt;/p&gt;

&lt;h2 id=&quot;but-why-does-it-still-happen&quot;&gt;But why does it still happen?&lt;/h2&gt;

&lt;p&gt;The real question is why Adsense still allows these ads to come back. They are clearly against the Adsense policies (&lt;a href=&quot;https://support.google.com/adspolicy/answer/6020955&quot;&gt;Misrepresentation - Misleading ad design&lt;/a&gt;): Ads that make it difficult for the user to understand that they are interacting with an ad are not allowed.&lt;/p&gt;

&lt;p&gt;So why does Adsense keep serving them? The ads should be fairly simple to detect and block. Come on, it’s 2024, you’re Google and have AI and stuff. Every site owner spending countless hours blocking each ad individually every time they come back after a day or two, is clearly not a viable solution. Google/Adsense have to solve this on their side!&lt;/p&gt;

&lt;p&gt;Google &lt;a href=&quot;https://blog.google/products/ads-commerce/google-ads-safety-report-2023/#enforcement&quot;&gt;claims&lt;/a&gt; to use machine learning and AI to block ads. But even the most rudimentary AI in 2024 should easily be able to detect a big fat green button saying “CONTINUE”. However, the ads keep coming back.&lt;/p&gt;

&lt;p&gt;What’s wrong, Adsense?&lt;/p&gt;
</description>
        <pubDate>Thu, 28 Mar 2024 00:00:00 +0000</pubDate>
        <link>https://blog.kittysplit.com/en/adsense-is-broken/</link>
        <guid isPermaLink="true">https://blog.kittysplit.com/en/adsense-is-broken/</guid>
      </item>
    
  </channel>
</rss>
