<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[IceRock Development - Medium]]></title>
        <description><![CDATA[A boundlessly imaginative mobile development team from the depths of Siberia and its capital, the city of Novosibirsk. Let’s meet on www.icerockdev.com - Medium]]></description>
        <link>https://medium.com/icerock?source=rss----b74ae24564e---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>IceRock Development - Medium</title>
            <link>https://medium.com/icerock?source=rss----b74ae24564e---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 14:06:34 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/icerock" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[KMP vs Flutter vs React Native]]></title>
            <link>https://medium.com/icerock/kmp-vs-flutter-vs-react-native-cb7d1958fab3?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/cb7d1958fab3</guid>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[flutter]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 28 Apr 2023 03:14:57 GMT</pubDate>
            <atom:updated>2023-05-10T11:51:33.767Z</atom:updated>
            <content:encoded><![CDATA[<p><em>There is a wide range of options in cross-platform nowadays, among them Flutter, React Native, and, of course, Kotlin Multiplatform Mobile. Which technology should you choose, and why this one? Let’s try to clear out!</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X_42TuA0mlasrPrr2kEH0A.png" /></figure><p>All technologies have their advantages and disadvantages, resolve particular problems, but never all of them. Thus, your choice will depend on specific tasks.</p><p>Guys from Jetbrains have made an incredible attempt to optimize development costs and reuse the code. Moreover, such reuse is possible not only within one platform but also between different platforms (Android and iOS). If your development team wants to keep native UI for each platform, then Kotlin Multiplatform Mobile is your perfect option.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t-lC5PoH-vi3eoOsiH439A.png" /></figure><p>It is due to the fact that Kotlin Multiplatform doesn’t do UI at all. It, instead, does business logic for Android and iOS apps. Because of the special configuration of the project, it is possible to make a project that will be compiled in the Android library with specific code for Android, and in the iOS framework with the corresponding code for iOS. From a technical perspective, there are no more problems with using the same code on iOS and Android at the same time. When you debug your code, you only need to edit it once, and changes will happen on both platforms. However, on the native side of applications, it would be better not to share the following:</p><ul><li>the whole interface;</li><li>binding the interface to the shared Viewmodel from the shared code library;</li><li>platform-specific features: work with camera, NFC, Touch ID, or Bluetooth.</li></ul><p>And in any case, sharing UI code across platforms is not necessary. This usually requires several iterations to make the user UI and behave more natively. This, in turn, requires additional development cycles.</p><p>Flutter uses canvas from native SDK of different platforms and draws its UI component on that canvas using Material design specifications. React Native uses native components mapped to js code. React makes it possible to update the application logic without rebuilding and re-uploading to the store (the js code is downloaded from the servers and immediately starts working on the device, without updating the entire application).</p><p>As for the business logic, it is also common in Flutter and React native, but written in Dart and JavaScript respectively, which mobile developers accustomed to the native stack are not familiar with. That is, these platforms will require a communication bridge between native and non-native code.</p><p>The design consistency for all versions of the operating system and between the operating system is the benefit of Flutter as well. This technology also allows you to easily create your own custom design. It is hardly possible to compare the high development speed and high productivity of Flutter with other technologies, as it is definitely its undoubted advantage.</p><p>There are no intermediate layers to deal with in Kotlin Multiplatform Mobile, which virtually eliminates any interop bottlenecks. And because Kotlin Multiplatform works with the native platform ecosystems, developers can use the tools and libraries they’ve always used, including SwiftUI and Jetpack Compose. You may code around the limitations with Kotlin, Swift, or any other language. But in the case of Flutter, we have to stick to Dart only and in React Native we have to stick with js only.</p><p>Kotlin Multiplatform doesn’t require a VM, while React Native does. Even though Flutter also doesn’t require a VM in production, you should write in a non-native language in a non-native ecosystem. At the same time, it is very easy to write native code at any level of coding and at any layer of the architecture in Kotlin Multiplatform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OyK8R-FitWLKjAYhVbEwXA.png" /></figure><p>Unlike Flutter or React Native, where you should proceed with their infrastructure, KMM has the power to get integrated with any existing project. ​​Kotlin Multiplatform does not become its own ecosystem but works with ecosystems of the native platform. You may use those tools and libraries you’re used to, including SwiftUI and Jetpack Compose. Any limitations you face can always be coded around using Kotlin, Swift, or any other language that is convenient for solving this specific problem with the least risk.</p><p>Implementation of KMM can be carried out step by step, without the need to stop the current development process. We are preparing a roadmap for expanding the use of KMM in the project so that after each step a release to the store can be carried out. It is also important that we are expanding the use of KMM with the current application code, which helps to save the project from new bugs.</p><p>In the KMM project, only one specialist is required to make the common code and the native side of its platform, and it will be required to connect a specialist from another platform only to embed UI to existing logic. The code is written only once, which eliminates the risk of having two sets of bugs instead of one, which you will get here.</p><p>As a result, the support and development of the application become much cheaper, more efficient, the implementation of various features will also be noticeably faster and at the same time, you will save full nativeness for the developer and user.</p><blockquote>Read more about KMM <a href="https://icerockdev.com/blog/kotlin-multiplatform">in our blog</a>.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb7d1958fab3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/kmp-vs-flutter-vs-react-native-cb7d1958fab3">KMP vs Flutter vs React Native</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimize or Die. Profiling and Optimization in Jetpack Compose]]></title>
            <link>https://medium.com/icerock/optimize-or-die-profiling-and-optimization-in-jetpack-compose-a165c8897b3f?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/a165c8897b3f</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 28 Apr 2023 03:14:45 GMT</pubDate>
            <atom:updated>2023-06-05T10:03:23.981Z</atom:updated>
            <content:encoded><![CDATA[<p>Your brand-new app gets bombarded with user complaints on Google Play about it hanging or slowing down? Follow the tips in this article to fix it.</p><p>Hello! My name is Sergey Panov, and I am a mobile developer at IceRock. Today, I will be using our Campus app as an example to demonstrate profiling and optimization techniques for Jetpack Compose.</p><p>Campus is an app that allows students to view their class schedule. Its main feature is the schedule screen consisting of two <a href="https://google.github.io/accompanist/pager/">pagers</a> for weeks and days. When users tried to swipe them, it caused the app to hang, but we managed to fix it.</p><p>Here are the topics discussed in the article:</p><ol><li>Recomposition Counts. Pinpointing Excessive Recompositions.</li><li>Compose Compiler Metrics. Identifying the Root Causes of Excessive Recompositions.</li><li>CPU Profiling. Finding “Hot” Methods and Freeing up the CPU.</li><li>GPU Profiling. Learning Which Components Take a Long Time to Draw.</li><li>More Tips on Fixing Bugs Identified with Profiling Tools.</li></ol><h3>Recomposition Counts. Pinpointing Excessive Recompositions</h3><p><strong>Issue.</strong> The first concept we need to highlight is that of <a href="https://developer.android.com/jetpack/compose/mental-model#recomposition">recomposition</a>.</p><p>Rendering a screen in Compose consists of traversing the graph of <a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/Composable">composable</a> functions. If a graph node changes its state (i.e. arguments of a composable method, values of <strong><em>MutableState</em></strong> or animations change), the subgraph is updated, that is, the functions are called again. Such an update is called a recomposition.</p><p>Recompositions are fast and should not present any issues, but frequent recompositions can cause an app to slow down.</p><p>This is a distinctive feature and a pitfall of Compose. After all, a developer can make some mistakes leading to excessive recompositions, for instance, recreate an object instead of reusing it. This results in unnecessary computations and performance issues.</p><p><strong>Solution.</strong> In large projects, these mistakes might be tricky to detect by the unaided eye, so Android Studio provides our eyes with an aid — a tool called <a href="https://developer.android.com/jetpack/compose/tooling#recomposition-counts">Recomposition Counts</a>.</p><p>It can be enabled in Layout Inspector to see how often a composable function is called or skipped. <a href="https://developer.android.com/jetpack/compose/tooling#recomposition-counts">Follow this guide</a> to display these stats.</p><p><strong>Application. </strong>Let’s use Campus as an example to check whether we have any excessive recompositions. We run the project in the emulator and navigate to Layout Inspector. After we’ve enabled Recomposition Counts, two new columns appear that show the number of recompositions and skips for each composable method. Now we can see that, with each swipe to the next day, <strong><em>ScheduleDayPage</em></strong> and <strong><em>RemoteStateContent</em></strong> methods are recomposed three times instead of one and don’t get skipped at all.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*We5hTrV83Ojv5815" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DI8M0_Yi1q6uNOcu" /></figure><p>This means that we have managed to pinpoint the issue and identify the method that requires a closer look:</p><pre>@Composable<br>fun ScheduleDayPage(<br>    state: RemoteState&lt;ScheduleDay&gt;,<br>    onItemClick: (ScheduleDetails) -&gt; Unit,<br>    viewAds: (ScheduleDay.Lesson) -&gt; Unit,<br>    clickOnAds: (ScheduleDay.Lesson) -&gt; Unit<br>) {<br>    ...<br>}</pre><h3>Compose Compiler Metrics<a href="https://kmm.icerock.dev/learning/android/profiling#composable-metrics">​</a>. Identifying the Root Causes of Excessive Recompositions</h3><h3>Stable data types<a href="https://kmm.icerock.dev/learning/android/profiling%23%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D1%82%D0%B8%D0%BF%D1%8B">​</a></h3><p><strong>Issue.</strong> To understand why a method can be recomposed multiple times, we need to learn the concept of <a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable">stability</a>.</p><p>Stability allows the compiler to be sure that the type either does not change or notifies the composition when a change occurs. The compiler does not check whether a composable method has changed, if all of its arguments are stable.</p><p>Thus, stable data types are such types that either produce immutable instances or notify the composition about their state changes. Besides stable and unstable data types, there is also the third type — immutable. This is a more strict type that ensures that the object does not change at all.</p><p>To put it more rigorously, a stable type must satisfy the following conditions:</p><ol><li>The result of <strong><em>equals</em></strong> will always return the same result for the same two instances.</li><li>When a public field of the class changes, the composition will be notified.</li><li>All public field types are stable.</li></ol><p>For the third condition to be satisfied, there must be stable types that developers could use to create their own stable data types. Jetpack Compose Compiler considers the following types stable: primitive types, String, function types, enumerations.</p><p>To gain deeper insight into the recomposition and stable data type concepts, I recommend reading <a href="https://medium.com/@denisgolubev1999/jetpack-compose-%D0%BF%D0%BE%D0%B4-%D0%BA%D0%B0%D0%BF%D0%BE%D1%82%D0%BE%D0%BC-%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F-%D0%B8-%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D1%82%D0%B8%D0%BF%D1%8B-9598f8b62006">this article by Denis Golubev</a>. It demonstrates the following example:</p><pre>// All the class fields are immutable. This is a stable class.<br>class StableClass1(<br>    val immutableValue: Int<br>)<br><br>// Composition knows when the state changes thanks to MutableState.<br>// This is a stable class as well.<br>class StableClass2(<br>  val mutableState: MutableState&lt;String&gt;<br>)<br><br>// There are mutable fields. This is an unstable class.<br>class UnstableClass1(<br>  var immutableValue: String<br>)<br><br>// A field belongs to an unstable type. This is also an unstable class.<br>class UnstableClass2(<br>  val unstableTypeField: UnstableClass1<br>)</pre><blockquote><em>Developers can also mark classes with the </em><strong><em>@Stable</em></strong><em> and </em><strong><em>@Immutable</em></strong><em> annotations.</em></blockquote><p><strong>Solution.</strong> To identify stable and unstable types, as well as skippable and restartable methods, which we detected with Recomposition Counts, we can use the <a href="https://chris.banes.dev/composable-metrics/">Compose Compiler Metrics</a> tool.</p><p>To get stats for the project, we need to add a task into <em>app/build.gradle.kts</em> and run the release build with the respective compiler flag enabled, as described in the article.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/684/0*W5b4dCjQs3ZxAWSM" /></figure><p>As a result, we will see four files in the <em>build/compose_metrics</em> folder that contain the following:</p><ul><li><em>app_release-classes.txt</em> contains information on class stability:</li></ul><pre>unstable class MigrationScreen {<br>  unstable val navController: NavController<br>  &lt;runtime stability&gt; = Unstable<br>}<br>stable class ExpandedStateStrings {<br>  stable val expandString: String<br>  stable val collapseString: String<br>  &lt;runtime stability&gt; = Stable<br>}</pre><ul><li><em>app_release-composables.txt</em> contains information on whether methods are recomposable or skippable:</li></ul><pre>restartable skippable scheme(&quot;[androidx.compose.ui.UiComposable]&quot;) fun TopAppBarTitle(<br>  stable modifier: Modifier? = @static Companion<br>  stable name: String<br>  stable date: String<br>  stable weekName: String?<br>  stable menuExpanded: MutableState&lt;Boolean&gt;<br>)</pre><ul><li><em>app_release-composables.csv</em> contains the same details but presented as a table.</li><li><em>app_release-module.json</em> contains general information on the project:</li></ul><pre>{<br>&quot;skippableComposables&quot;: 693,<br>&quot;restartableComposables&quot;: 838,<br>&quot;readonlyComposables&quot;: 0,<br>&quot;totalComposables&quot;: 882,<br>...<br>}</pre><p><strong>Application.</strong> Let’s get back to Campus: we were interested in the <strong><em>ScheduleDayPage</em></strong> method. To find the information on it, we will check the <em>app_release-composables.txt</em> file:</p><pre>restartable scheme(&quot;[androidx.compose.ui.UiComposable]&quot;) fun ScheduleDayPage(<br>unstable state: RemoteState&lt;ScheduleDay&gt;<br>stable onItemClick: Function1&lt;ScheduleDetails, Unit&gt;<br>stable viewAds: Function1&lt;Lesson, Unit&gt;<br>stable clickOnAds: Function1&lt;Lesson, Unit&gt;<br>)</pre><p>As we can see, the method is not <strong><em>skippable</em></strong> and will be recomposed whenever applicable. We can also note that <strong><em>state</em></strong> is not a stable argument.</p><p>To fix this, we can annotate <strong><em>RemoteState</em></strong> and <strong><em>ScheduleDay</em></strong> classes as <strong><em>@Immutable</em></strong> after ensuring that these classes will not change after they are constructed.</p><blockquote><em>Do not “slap” this annotation on classes with var fields or fields that contain lists.</em></blockquote><p>This will resolve the issue of class instability, but we are not done with the method just yet. The metric has it marked as <strong><em>skippable</em></strong>, but in Layout Inspector we can still see excessive recompositions.</p><h3>Unstable lists<a href="https://kmm.icerock.dev/learning/android/profiling%23%D0%BD%D0%B5%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D1%81%D0%BF%D0%B8%D1%81%D0%BA%D0%B8">​</a></h3><p><strong>Issue.</strong> There is a way to override class stability with annotations, but it does not solve the stability issues with lists, sets, and maps.</p><p><strong>Solution.</strong> Chris Ward offers <a href="https://christopherward.medium.com/debugging-and-fixing-a-huge-jetpack-compose-performance-problem-in-my-sudoku-solver-app-8f67fa229dc2">a solution to this problem</a> that uses the <a href="https://github.com/Kotlin/kotlinx.collections.immutable">kotlinx-collections-immutable</a> library, which allows you to specify that a composable method should take an immutable list as an argument.</p><pre>@Composable<br>fun StableGrid(<br>    values: ImmutableList&lt;GridItem&gt;<br>) {<br>    ...<br>}</pre><h3>Unstable lambdas<a href="https://kmm.icerock.dev/learning/android/profiling%23%D0%BD%D0%B5%D1%81%D1%82%D0%B0%D0%B1%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BB%D1%8F%D0%BC%D0%B1%D0%B4%D1%8B">​</a></h3><p><strong>Issue.</strong> Our <strong><em>ScheduleDayPage</em></strong> class has function arguments as well, which you should be careful with in Compose.</p><p>Let’s check out the method initialization part:</p><pre>@Composable<br>internal fun ScheduleScreenContent(<br>    selectedDate: LocalDate,<br>    onDateSelected: (LocalDate) -&gt; Unit,<br>    onItemClick: (ScheduleDetails) -&gt; Unit,<br>    viewAds: (LocalDate, ScheduleDay.Lesson) -&gt; Unit,<br>    clickOnAds: (LocalDate, ScheduleDay.Lesson) -&gt; Unit,<br>    scheduleDayForDate: (LocalDate) -&gt; StateFlow&lt;RemoteState&lt;ScheduleDay&gt;&gt;<br>) {<br>    CalendarDayPager(<br>        selectedDate = selectedDate,<br>        onDateSelected = onDateSelected,<br>        dayContent = { date -&gt;<br>            val scheduleDayFlow: StateFlow&lt;RemoteState&lt;ScheduleDay&gt;&gt; = remember(date) {<br>                scheduleDayForDate(date)<br>            }<br>            val scheduleDay: RemoteState&lt;ScheduleDay&gt; by scheduleDayFlow.collectAsState()<br>            ScheduleDayPage(<br>                state = scheduleDay,<br>                onItemClick = onItemClick,<br>                viewAds = { lesson -&gt; viewAds(date, lesson) },<br>                clickOnAds = { lesson -&gt; clickOnAds(date, lesson) }<br>            )<br>        }<br>    )<br>}</pre><p>Take a look at how we pass functions to our <strong><em>ScheduleDayPage</em></strong> method.</p><p>Compose has a concept of unstable lambdas, which has been <a href="https://multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/">described in great detail by Justin Breitfeller</a>.</p><p>One of the highlights of his article is how the compiler processes lambdas; namely, it creates an anonymous class with an <strong><em>invoke()</em></strong> method to put lambda content in it. In other words, every time we pass a lambda, we create an object of the anonymous class, which does not have a hash that would allow the compiler to compare it at the recomposition stage. So, the compiler thinks that the graph node state has changed and a recomposition is required.</p><blockquote><em>Thus, Compose Compiler Metrics does not mark lambdas as unstable, yet recomposition still takes place.</em></blockquote><p>In addition to passed arguments, lambdas may have external arguments (e.g., context), which might result in differences between the classes generated by the compiler.</p><p><strong>Solution.</strong> The same article presents four ways to deal with this issue.</p><p><strong>1. Method references.</strong> By using method references instead of lambdas, we can prevent a new class from being generated. Method references are stable function types and will remain equivalent between recompositions.</p><pre>// Instead of a lambda…<br>{ lesson -&gt;<br>    viewModel.playHooky(lesson)<br>}<br>// use a method reference<br>viewmodel::playHooky</pre><p><strong>2. Remember.</strong> Another option is to remember the lambda instance between recompositions. This will ensure the exact same instance of the lambda will be reused upon further compositions.</p><pre>// Create a remembered object and pass it on initialization<br>val playHookyRemember: (Lesson) -&gt; Unit = remember { { viewModel.playHooky(it) } }</pre><p><a href="https://developer.android.com/jetpack/compose/performance%23use-remember"><em>Android </em></a><a href="https://developer.android.com/jetpack/compose/performance#use-remember"><em>documentation</em></a><em> recommends making use of </em><strong><em>remember</em></strong><em>.</em></p><p><strong>3. Static functions.</strong> If a lambda is simply calling a top-level function, the compiler considers it stable, since top-level functions do not take external arguments like context.</p><p><strong>4. <em>@Stable</em> type in a lambda.</strong> As long as a lambda is only capturing other stable types it will not be updated by the compiler on graph recomposition.</p><pre>var skippedLessons by remember { mutableStateOf(listOf(&quot;Biology&quot;, &quot;Geography&quot;, &quot;Chemistry&quot;)) }<br>Schedule(<br>    playHooky = { lesson -&gt;<br>        skippedLessons += lesson<br>    }<br>)</pre><p><strong>Application. </strong>Going back to Campus, let’s use this new knowledge to fix incorrect lambda passes like so:</p><pre>@Composable<br>internal fun ScheduleScreenContent(<br>    selectedDate: ComposeDate,<br>    onDateSelected: (LocalDate) -&gt; Unit,<br>    onItemClick: (ScheduleDetails) -&gt; Unit,<br>    viewAds: (LocalDate, ScheduleDay.Lesson) -&gt; Unit,<br>    clickOnAds: (LocalDate, ScheduleDay.Lesson) -&gt; Unit,<br>    scheduleDayForDate: (LocalDate) -&gt; StateFlow&lt;RemoteState&lt;ScheduleDay&gt;&gt;<br>) {<br>    CalendarDayPager(<br>        selectedDate = selectedDate,<br>        onDateSelected = onDateSelected,<br>        dayContent = { date -&gt;<br>            val scheduleDayFlow: StateFlow&lt;RemoteState&lt;ScheduleDay&gt;&gt; = remember(date) {<br>                scheduleDayForDate(date.toLocalDate())<br>            }<br>            val scheduleDay: RemoteState&lt;ScheduleDay&gt; by scheduleDayFlow.collectAsState()<br><br>           // Lambda is moved to a separate object with ‘remember’<br>            val viewAdsRemember: (ScheduleDay.Lesson) -&gt; Unit =<br>                remember(date) { { lesson -&gt; viewAds(date.toLocalDate(), lesson) } }<br><br>           // Lambda is moved to a separate object with ‘remember’<br>            val clickOnAdsRemember: (ScheduleDay.Lesson) -&gt; Unit =<br>                remember(date) { { lesson -&gt; clickOnAds(date.toLocalDate(), lesson) } }<br><br>            ScheduleDayPage(<br>                state = scheduleDay,<br>                onItemClick = onItemClick,<br>                viewAds = viewAdsRemember,<br>                clickOnAds = clickOnAdsRemember<br>            )<br>        }<br>    )<br>}</pre><h3>CPU Profiling. Finding “Hot” Methods and Freeing up the CPU</h3><h3>CPU Profiler</h3><p><a href="https://developer.android.com/studio/profile/cpu-profiler">CPU profiling</a> has to be the most powerful tool against app hanging. On top of that, optimizing CPU usage of your app has many other benefits, such as faster and smoother user experience and improving battery life of the device.</p><p>You can use the profiler to inspect CPU usage of your app and thread activity during interaction with the app.</p><p><strong>Application.</strong> <a href="https://medium.com/androiddevelopers/spot-your-ui-jank-using-cpu-profiler-in-android-studio-9a4c41a54dab">This article by Takeshi Hagikura</a> is a good guide on getting the CPU profiler up and running. Let’s figure out what these stats are good for using Campus as an example. Run the project in the emulator and go to the Profiler tab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/911/0*KSZfxIFPz9ntYdME" /></figure><p>Once the app is initialized, the CPU, Memory, and Energy graphs will be displayed. We will focus on CPU stats. In its detailed view you should see the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WsV7AgMJTBerL4c4" /></figure><p>To record the stats, click Record, interact with the app for a while, and click Stop.</p><p>After the record is created, you should see the following screen showing CPU usage over the recorded interval (1), app interaction stats (2), threads (3), and detailed thread analysis (4).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fH0P8hgoaLn8XTLN" /></figure><h3>Flame Chart<a href="https://kmm.icerock.dev/learning/android/profiling%23flame-chart">​</a></h3><p>Next, we will focus on the Flame Chart tab, which contains a chart of function calls with associated CPU usage time. This chart helps with identifying the processes that run longer than expected, which we can optimize.</p><p>First, in the CPU Usage window, we select the interval that we are interested in. Then we can pinpoint it in the Threads window. Let’s select the most prominent bars.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*f4tIA1SEDncnBAZP" /></figure><p>Here are the values we should focus on:</p><p><strong>1. Component draw time.</strong> The standard refresh rate for the majority of smartphone screens is 60 Hz. That means that the displayed image is fully updated 60 times a second or every 16.67 milliseconds.</p><p>So, for the UI to be smooth, this should be the maximum time for drawing a component. Thus, pay attention to the “hottest” methods.</p><blockquote><em>To calculate the drawing time, consider the proportion of values in Flame Chart to the number of seconds in the selected interval. The exact interval duration can be found in the Summary tab.</em></blockquote><p><strong>2. CPU usage. </strong>Try to keep the CPU idle most of the time, let it stay “cool”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/824/0*Rq-QLufoORxW8s_O" /></figure><p><strong>3. Components we can influence.</strong> You can select those using the search bar. For instance, if you search for the project name, the profiler will highlight parts of the “flame” with your methods in color and the text in method bars in bold.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/834/0*J9gsXrU6e5ea6pj_" /></figure><p><strong>4. Methods we can move to another thread.</strong> Some intensive tasks can be separated into another thread. For instance, that could be interactions with databases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/709/0*pJVGwSWjS5IbEVRQ" /></figure><p><em>Read </em><a href="https://russianblogs.com/article/9627812896/"><em>this article</em></a><em> for more details on CPU profiler capabilities.</em></p><p><strong>Application</strong><a href="https://kmm.icerock.dev/learning/android/profiling%23%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-2"><strong>​</strong></a><strong>.</strong> Now let’s go back to Campus. In our case we have swipes over the schedule screen that we would like to optimize. Let’s pick one of these in the CPU Usage window and select the main thread in the Threads window. Then we can search for our project: this highlights three methods that take up a lot of CPU time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sgye-G_Nn-LcltZk" /></figure><p>We are going to examine just one of them. The <strong><em>WeekPage</em></strong> method takes up a whopping 400 milliseconds in the 4 seconds interval. To calculate it more precisely, we should average over several values. Take note of the approximate value for CPU time used on this method: 95 milliseconds.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/664/0*pMlNdyWIKBiqqqYs" /></figure><pre>@Composable<br>fun WeekPage(<br>    startOfWeek: LocalDate,<br>    selectedDate: LocalDate,<br>    currentDate: LocalDate,<br>    onDateSelected: (LocalDate) -&gt; Unit<br>) {<br>    Row(<br>        modifier = Modifier<br>            .fillMaxWidth()<br>            .padding(horizontal = 16.dp),<br>        horizontalArrangement = Arrangement.SpaceBetween<br>    ) {<br>        DayOfWeek.values().forEach { dayOfWeek -&gt;<br>            val date: LocalDate = startOfWeek.plus(DatePeriod(days = dayOfWeek.ordinal))<br><br>            val simpleDateFormat = SimpleDateFormat(&quot;EE&quot;, Locale.getDefault())<br>            val dayOfWeekName = simpleDateFormat.format(date.toJavaDate()).uppercase()<br><br>            val shape = RoundedCornerShape(8.dp)<br>            Box(...) { ... }<br>        }<br>    }<br>}</pre><p>Looking at our code, we can see an obvious flaw: <strong><em>SimpleDateFormat </em></strong>is initialized in the loop body for each <strong><em>Row</em></strong>. We can fix this by moving the initialization away from <strong><em>Row</em></strong> and by using <strong><em>remember</em></strong>.</p><p>After the fix, let’s check the results. By doing this, we reduced the time it takes to draw <strong><em>WeekPage</em></strong> to 60–70 milliseconds (the image shows an interval of about 1 second):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/836/0*Kl6X7pAl-NGy2IJr" /></figure><h3>System Trace<a href="https://kmm.icerock.dev/learning/android/profiling%23system-trace">​</a></h3><p>You can also get CPU usage stats focused on composable methods only. To do so, use the Jetpack Compose Composition Tracing tool.</p><p><em>Jetpack Compose Composition Tracing is available starting with the following versions:</em></p><ul><li><em>Android Studio Flamingo Canary 1,</em></li><li><em>Compose UI 1.3.0-beta01,</em></li><li><em>Compose Compiler 1.3.0.</em></li></ul><p>Compose Composition Tracing can display composable functions of Jetpack Compose in System Trace Android Studio Flamingo. Follow <a href="https://medium.com/androiddevelopers/jetpack-compose-composition-tracing-9ec2b3aea535">the instructions in the article by Ben Trengrove</a> and install the appropriate version of Android Studio, then add the dependency into <em>app/gradle.kts</em>.</p><p><strong>Application.</strong> Select the System Trace configuration in CPU profiler, click Record, interact with the app for a while, and click Stop.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/823/0*f5zylGRXfp0_f2V0" /></figure><p>After the record is created, you should see the following screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Pnt-1q2F99KpPxDx" /></figure><p>When checking the system trace, you can view trace events in the thread timeline to see the details on the events occurring in every thread.</p><p>We have several new tabs: Display (1), Frame Lifecycle (2), CPU Cores (3), and Process Memory (4). The Threads tab looks a bit different and displays a chart of composable function calls now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fQZ9uj64Itm4e0Ew" /></figure><p>These tabs show activity for each core, the size of physical memory that the app uses currently, and more.</p><p>The Threads tab displays a call chart, while the detailed thread stats also show Flame Chart, where only composable method stats are shown.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MZhUYsWuuAZJgbKO" /></figure><p>In Threads, you can click a method to search for it across the entire recorded interval and see how many times the thread was called and the average time spent for each call.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*t2YpendJDbOgPjSl" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OGaWKAQ1hbVT50ho" /></figure><p><em>Read </em><a href="https://developer.android.com/studio/profile/inspect-traces"><em>the official documentation</em></a><em> for more details on System Trace capabilities.</em></p><h3>GPU Profiling. Learning Which Components Take a Long Time to Draw</h3><p>Another important profiling tool is the <a href="https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering">GPU profiler</a>.</p><p>The documentation states that the Profile GPU Rendering tool displays a scrolling bar chart showing the time it takes to render frames of the user interface against the reference value of 16.67 milliseconds per frame.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/650/0*vWDLI9mCnoYEZw0y" /></figure><p>To use this profiler, you will need a device running Android 4.1 (API level 16) or later. A guide on enabling it is available in the documentation.</p><p>The green horizontal line represents the value of 16.67 milliseconds. To reach 60 frames per second, the vertical bar for each frame has to remain below this line. Every time a bar goes above this line, the animation may stutter.</p><p>The documentation describes <a href="https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering#gpu_rendering_output">bar color coding</a>.</p><p><strong>Application.</strong> Using Campus as an example we can take note of prominent blue, light green, and dark green bars.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kOvWx7-d5QE1j6IL" /></figure><p>This shows us that creating and updating lists (blue bars) takes long, which could be because we have many custom views or <strong><em>onDraw</em></strong> methods do some intensive work. Processing <strong><em>onLayout</em></strong> and <strong><em>onMeasure</em></strong> methods (light green bars) takes a long time as well, which might indicate that a complex view hierarchy is drawn. On top of that, a lot of time is spent on animators performed for the views and on handling input callbacks (dark green); view bindings during scrolling, such as <strong><em>RecyclerView.Adapter.onBindViewHolder()</em></strong>, also typically occur during this segment and are the most common source of slowdowns in it.</p><p>The next image shows a chart of the app after various optimizations described above.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_sZMKekfp2P4DK57" /></figure><p>The results show that there is still room for optimization.</p><h3>More Tips on Fixing Bugs Identified with Profiling Tools</h3><p>I gathered a few tips from <a href="https://developer.android.com/jetpack/compose/performance#best-practices">the official documentation</a> and <a href="https://proandroiddev.com/7-things-to-keep-in-mind-while-building-jetpack-composables-7e4a5ecaa8b0">this article by Mukesh Solanki</a>.</p><p><strong>1. Always use <em>remember</em> in composable methods.</strong> Recomposition can occur at any time for a number of reasons. If you have a value that is supposed to survive a recomposition, <strong><em>remember</em></strong> will help you keep it that way.</p><p><strong>2. Use lazy layouts only when necessary.</strong> Using <strong><em>LazyRow</em></strong> for a list of five items can slow rendering down significantly.</p><p><strong>3. If at all possible, refrain from using <em>ConstraintLayout</em>.</strong> Use <strong><em>Column</em></strong> and <strong><em>Row</em></strong> instead. <strong><em>ConstraintLayout</em></strong> is a system of linear equations, which requires more computations than generating elements one after another.</p><blockquote><em>Read more about design </em><a href="https://icerockdev.com/blog/ui-ux"><em>in our blog</em></a><em>.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a165c8897b3f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/optimize-or-die-profiling-and-optimization-in-jetpack-compose-a165c8897b3f">Optimize or Die. Profiling and Optimization in Jetpack Compose</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prototyping as a Quick and Cost-Effective Way to Test Hypotheses]]></title>
            <link>https://medium.com/icerock/prototyping-as-a-quick-and-cost-effective-way-to-test-hypotheses-7ee185ef9095?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/7ee185ef9095</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[mobile-apps]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[prototyping]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 28 Apr 2023 03:14:33 GMT</pubDate>
            <atom:updated>2023-05-10T13:06:47.630Z</atom:updated>
            <content:encoded><![CDATA[<p>Businesses are always looking for ways to launch apps quickly and with minimal development costs. There are various ways to do this: sketch your app design, launch apps from scratch with frameworks, or use builders. Different situations call for different solutions.</p><p>In this article, I will tell you about the approaches we use at IceRock. Our company specializes in outsource development of mobile apps, so we constantly look for new ways to speed up the development and enhance tried-and-true workflows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bIIqR3WzVPDx0-soVun-xg.png" /></figure><h3>Why a Prototype is Better than a Screen Map</h3><p>Before we start the development, we prepare one or several prototypes of the future app. This is more efficient than going straight to design or starting to develop the app right away.</p><p>A screen map is a set of images showing all app screens and their relationships. It visualizes the flow of the screens, but it doesn’t allow you to get a feel for the result and understand how usable the interface will be. In other words, it is less illustrative.</p><p>Clickable prototypes are more informative. We divide prototyping into two parts: we use a “wireframe prototype” before the design, and a “design prototype” after. A wireframe prototype is a schematic prototype that helps us understand the navigation and functionality aspects of an app. A design prototype is a product assembled once the UI design is complete. It is used to demonstrate the future app to the customer, for marketing purposes, and sometimes for testing. It also shows the developers how the final app is supposed to work.</p><p>After the design, we make a clickable prototype. The level of detail in this prototype can vary according to the client’s needs: it can be low-fidelity or high-fidelity.</p><p><strong>Low-fidelity prototypes consist of static images</strong> with active links to the next screens on the buttons. This is the type of prototype we <a href="https://youtu.be/pjFtlGc-v28">used for the Snow4u project</a>.</p><p><strong>High-fidelity prototypes are very similar to the app</strong> and fully interactive: you can type any text in the input fields, there are animations and transitions between the screens, and sometimes they can even perform complex calculations, such as commission on loan returns or currency conversion. For example, when developing the Ambit app, we <a href="https://www.youtube.com/watch?v=i8PEPxfAz3k">created a high-fidelity prototype</a> to work out all the little details of the interface.</p><p>The prototype explains the processes in the app better than the screen map thanks to its immersive nature. When used in a large project, a screen map turns into a maze that’s hard to navigate and blurs the logic of the app. But with a prototype, you focus on one screen at a time: the customer or developer just clicks the buttons as if the prototype were a full-fledged app, and everything becomes clear. As a result, the developer and the customer get a vivid example of the desired result, with a clear picture of how the elements would behave in different situations. This way you can solve most UX-related issues before development even starts.</p><h3>When One Prototype Just isn’t Enough — So You Make Several!</h3><p>There are cases where it’s more convenient to make several prototypes. This is what we <a href="https://icerockdev.com/cases/projects/primetime">did for the Primetime app</a>, for example, which is used for online orders and food delivery. We created separate prototypes for different types of orders and payment methods: first we made a prototype for pickup orders from food trucks, then another prototype for takeout orders, and a third prototype just for adding payment methods. The fourth and last prototype was for loading animations. Technically, this task could have been implemented within a single prototype, but in practice it was faster to build separate ones.</p><p>We chose this approach because Primetime was a complex app with several modules built over time, so we developed a dedicated prototype for each module. This approach allowed us to start developing one prototype while experimenting with the next one. This kept the experimenters from interfering with the programmers who were implementing the app based on the first prototype without any confusion. As we had separated the prototypes initially, we organized many subsequent stages along the same lines.</p><p>As things stand, it only makes sense to use this approach for large projects. What’s more, prototyping tools are evolving: at the time, this was the only optimal solution for a project of this scale. Nowadays, we tend to develop a single prototype for multiple use cases, an option made possible by the <a href="https://www.protopie.io/blog/using-interaction-recordings-to-collaborate-with-developers">new features of ProtoPie</a>, where we create our prototypes.</p><blockquote>Recently two of my “for fun” prototypes have been featured by ProtoPie. <a href="https://www.protopie.io/blog/fifteen-inspirational-prototypes-2022%23sound-wizard-prototype-by">The first one</a> lets you make a wizard move around using loud sounds. <a href="https://www.protopie.io/blog/fifteen-inspirational-prototypes-2022%23squid-game-prototype-by">And the second one</a> is a prototype for games from the Squid Game series.</blockquote><p>Here’s another case study. Before developing an app for Russia Running, a sports event platform, we <a href="https://cloud.protopie.io/p/MYShAQs7tsG">created a prototype</a> that became the basis for the app. The prototype shows all the tabs, click animations and loading screens. You can enter search queries or adjust the filters. For this app, we <a href="https://cloud.protopie.io/p/830961241e/r/92e82c6a">developed a prototype feature that allows you to root for a particular competitor</a> and watch them move along the track in real time. Thanks to this work, the developers were able to accurately assess how much the app would cost. This detailed prototype cost USD 1,500 and took only three weeks to make.</p><p>In the same prototype, we <a href="https://cloud.protopie.io/p/830961241e/r/01ca0bc1">tested all the animation parameters for the pie charts</a> that show the tournament participants’ statistics. This made things easier for the programmer, allowing them to transfer the timings and animation variables from the prototype into the code and saving them time. Without a prototype, it’s almost impossible to complete an animation at the first attempt: it is hard to explain how it works without a visual example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pIl273PX413bSls0HukMqA.gif" /><figcaption><em>In the Russia Running app prototype, you can test the whole interface: buttons, drop-down menu, search function, filters, and calendar. </em>On the left is the prototype. on the right is the app</figcaption></figure><h3>Why We Write Code Manually During Implementation Instead of Using Code Generators</h3><p>When the prototype is ready and approved, developers start implementing the idea. Of course, there are external tools that can help turn a static design into a piece of code or a finished app. For example, Supernova Studio allows you to apply primitive logic to a static design, and then generates code that can be assembled into an app.</p><p>But the generated code has the following issues:</p><ol><li>The prototype doesn’t indicate what data is needed for the backend and where it should come from: it is still the programmer’s job to specify this.</li><li>The program generates suboptimal code, and it takes just as much time to make this code readable and optimized as to create similar code from scratch.</li><li>Adjusting your design and ideas to the limitations of app builders is also difficult and can be outright impossible.</li></ol><p>This is why developers still need to get their hands dirty when turning a prototype into a full-fledged app, although we are constantly looking for ways to optimize this process.</p><h3>How Prototypes Can Help When Creating an App</h3><p><strong>Can be used to test hypotheses.</strong> You can show a prototype to a focus group or an investor, get feedback, study the needs of the target audience, and decide on further development.</p><p><strong>Helps to demonstrate an idea. </strong>When investors can experience something close to the final app, it is easier to impress them and convince them to invest in full-scale development.</p><p><strong>Improves communication with the client.</strong> A prototype clearly shows what the end result will be like. If the client had something else in mind, you can still painlessly course-correct at this stage. As a result, the finished app will be exactly what the client is expecting, and you won’t have to spend time and money redoing it.</p><p><strong>Reduces risks at the development stage.</strong> When you’re identifying the client’s requirements, you can quickly build a wireframe prototype to make sure you don’t miss anything important when it comes to the project’s navigation and functionality. The developer will know how each button and screen of the future app is supposed to work. If something can’t be done or requires certain technologies, this will be clear before development starts.</p><p>And a high-fidelity prototype put together at the design stage enables you to do design reviews and easily find and fix logical and compositional errors in the UI design.</p><p><strong>Saves time.</strong> Creating a standard prototype takes one week, after which the client can test their idea and decide whether it is worth investing in full-scale development, which would take several months.</p><p>Within a single prototype, you can test various cases separately from each other and provide references to them in the ToR. This makes things easier for analysts, developers, and the client, and speeds up your work: while one case is being prototyped, the next team can start work on another.</p><p>For example, when a high-fidelity prototype is ready, QA specialists can get straight on with preparing multiple test cases for further testing of the app while it is being developed.</p><p><strong>Allows you to start your ad campaign early.</strong> Even before development begins, you can use the prototype for marketing purposes, e.g., on landing pages, in posts, in videos, etc.</p><p><strong>Saves money. </strong>Prototyping requires a separate budget, but it cuts development costs by reducing risks at the development stage. Moreover, even if the client’s idea turns out to be unsustainable, they’ve only spent USD 1,500 on a prototype, rather than USD 20,000 on developing a full-fledged app.</p><h3>Do You Always Need a Prototype?</h3><p>Absolutely not. Sometimes making an app prototype is not cost-effective, for instance, if your app is basic, with no problematic features or intricate artistic design.</p><p>For some features, a suitable prototyping tool doesn’t even exist yet. This is usually the case when the goal is to test a technical implementation rather than a visual one, such as implementation of augmented reality. We believe that within the next few years this kind of prototyping will become a reality too. For now, the developer has to come up with the behavior of this kind of element and implement it all on their own.</p><p>In other cases, when the customer wants to see the visual result of the programmers’ work, prototyping can be a quick and inexpensive way to demonstrate what an app is going to be like.</p><p>How are you doing with prototypes? Have they helped speed up development and made your programmers happier? Share your thoughts in the comments!</p><p>We’d be happy to consult on prototyping for teams and businesses, and we’re also available to help with development. You can contact us <a href="https://icerockdev.com/order">on this page</a>.</p><p><em>Follow our </em><a href="https://kz.linkedin.com/company/icerockdevelopment"><em>Linkedin account</em></a><em> to stay tuned.</em></p><blockquote><em>Read more about design </em><a href="https://icerockdev.com/blog/ui-ux"><em>in our blog</em></a><em>.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7ee185ef9095" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/prototyping-as-a-quick-and-cost-effective-way-to-test-hypotheses-7ee185ef9095">Prototyping as a Quick and Cost-Effective Way to Test Hypotheses</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Businesses Choose Kotlin Multiplatform Mobile: Native UI and Improvement Potential]]></title>
            <link>https://medium.com/icerock/why-businesses-choose-kotlin-multiplatform-mobile-native-ui-and-improvement-potential-c9093455552b?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/c9093455552b</guid>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[buisness]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 28 Apr 2023 03:13:53 GMT</pubDate>
            <atom:updated>2023-05-10T11:49:10.308Z</atom:updated>
            <content:encoded><![CDATA[<p>In most cases, entrepreneurs have an idea for what they would like an app to do, but are uncertain as to what technology would make it efficient, user-friendly and cost-effective. As experts in Kotlin Multiplatform Mobile, we will use this article to explore how this technology helped us create Moika-Moika (“Carwash-Carwash” in Russian) — a car wash search app for the Ucar company, and how our client benefited from it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U8JVvMOno3erkDYBNYygBg.png" /></figure><h3>What the client wanted</h3><p>The app had been commissioned by Ucar, a car wash marketplace. <a href="https://icerockdev.com/cases/projects/alfred/">Since we had had prior relationship with Ucar</a>, the client was already familiar with our approach and came to us specifically looking to take advantage of our expertise in KMM with plans to create two versions of the app — Android and iOS.</p><p>The product was supposed to solve the problem of access to car washes — the idea was to create something like Uber, but for car washes. At the same time, we needed to incorporate the client’s backend with up-to-date information on car washes in the area, such as operation hours, prices and promotions, as well as to set up a feature for service selection aтв payment.</p><p>The app was intended primarily for cab drivers, carsharing service providers, and commercial vehicle drivers. We also included a separate set of features for private car owners.</p><h3>How we made searching for car washes convenient</h3><p>The app helps car owners find the most suitable car wash on the map in terms of location, price and current promotions. For this we developed several features and provided access to up-to-date information.</p><ol><li>Moika-Moika shows car washes on the map with all the necessary information: working hours, prices, promotions, and free capacity status.</li><li>The app chooses rates depending on whether the user is a taxi driver or a regular car owner.</li><li>Users can pay for the services straight away in the app. This way, when they arrive at the car wash, the employees are already aware of which services they are supposed to provide and can identify the client’s car by the license plate.</li><li>You can add several cars to the app to find the best option for each vehicle. Services for passenger cars and large-sized vehicles are priced differently, so this can be a useful feature for multiple car owners.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*8-HB7xHmeR739TYP" /><figcaption><em>You choose a service in Moika-Moika like you would order a cab: select the service package and the rate and proceed to payment</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*3nYwuAtrcY899tx3" /><figcaption><em>The app allows you to get a discount via a promo code</em></figcaption></figure><h3>How the Moika-Moika app was developed</h3><p>We started the development from scratch. The task wasn’t difficult, so we had just one developer and a team leader involved. To accelerate the implementation, we used our MOKO libraries. We regularly create and update <a href="https://moko.icerock.dev/">KMM open-source libraries</a> to expand their applicability to specific tasks.</p><p>When creating the architecture, each feature (e.g., authorization, service selection, rate selection) was isolated into a separate module. This is a standard approach that allows developers to work on their own modules concurrently while not interfering with others.</p><p>We prefer to handle all aspects of development, but this time the client did the design and the backend by themselves and had us develop only the mobile app. Since the app was being developed at the same time as the server, the API configuration would sometimes get changed without coordination with our developer. This affected the launch date. For example, when the client would introduce a change in specifications which we would discover only towards the end of the sprint, it delayed the delivery date of the whole volume of performed works. To accelerate the development process, we agreed with the client to inform each other about any changes beforehand. This allowed us to avoid potential errors in the app.</p><h3>How long it took</h3><p>It took us two months to create the MVP app. The entire project, however, was seven months long.</p><p>We worked in two-week Agile sprints. The start of a sprint included task setting and distribution, and calls every other day with questions or preliminary results. At the end of the sprints, we released builds and received feedback on what modifications were required.</p><p>The app was released in summer 2021.</p><p>Currently, Ucar is cooperating with a large number of taxi companies and is <a href="https://play.google.com/store/apps/details?id=pro.ucar.carwash&amp;hl=en&amp;gl=US">receving a lot of positive feedback on the app</a>. As of now, we continue to support and update the app with works to introduce general UX improvements and update the bonus system in the pipeline.</p><h3>Why we used the Kotlin Multiplatform Mobile</h3><p>KMM has two major advantages: it allows you to write code for both platforms, Android and iOS, while implementing a fully native UI. In other words, the resulting app will look as envisioned by the designer, or the way the users of a given platform are accustomed to. This feature of KMM allowed us to develop the Moika-Moika app based on the design dlivered in advance by the client.</p><h3>How the client benefited from KMM</h3><p>We had already launched the Android version with a shared code and were ready to quickly create an iOS version, as originally planned. However, after the launch it became clear that the demand for an iOS version was still insufficient. Because of that, its development was shelved.</p><p>Ultimately, the customer didn’t incur any losses, since the cost of developing an Android version in KMM is the same as creating a native Android app. And if the demand for the iOS version increases, the client will be able to utilize the existing code to develop it faster and more cost-efficiently.</p><p>This is why both we and our clients appreciate KMM so much — it’s a reliable tool for developing complex projects. By writing a common code for iOS and Android, you can save money short-term due to shared logic compared to native development, and in the long term, you are able to save on labor costs for updates.</p><p>We’d be happy to consult teams and businesses on mobile app development with Kotlin Multiplatform Mobile or help with development. You can contact us <a href="https://icerockdev.com/order">on this page</a>.</p><blockquote>Read more about KMM <a href="https://icerockdev.com/blog/kotlin-multiplatform">in our blog</a>.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9093455552b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/why-businesses-choose-kotlin-multiplatform-mobile-native-ui-and-improvement-potential-c9093455552b">Why Businesses Choose Kotlin Multiplatform Mobile: Native UI and Improvement Potential</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Faster Development at a Lower Cost. Migration to Kotlin Multiplatform Mobile]]></title>
            <link>https://medium.com/icerock/faster-development-at-a-lower-cost-migration-to-kotlin-multiplatform-mobile-4f27518b99a5?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/4f27518b99a5</guid>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 28 Apr 2023 03:13:33 GMT</pubDate>
            <atom:updated>2023-05-10T13:18:28.573Z</atom:updated>
            <content:encoded><![CDATA[<p>Kotlin Multiplatform Mobile is a multiplatform tool that allows you to write universal code for iOS and Android, while saving up to a third of your budget and speeding up the development by 25%. But what if you already have a project, and you want to migrate to KMM? In this article we talk about what type of multiplatform migration costs $1300, $6000 and $40000.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WERQLLiYjZd1FkAaW3axjA.png" /></figure><h3>When should you integrate KMM?</h3><p>You can migrate to Kotlin Multiplatform Mobile both at the start of your development and at the implementation or launch stage. In order for the migration to make sense, one or more of the following conditions should apply:</p><ol><li>You already have an app for multiple platforms, Android and iOS, or you have just started developing such an app.</li><li>Your app with Android and iOS versions has a complex business logic but a simple UI. For instance, it uses offline synchronization.</li><li>You already have an Android app in Kotlin, but you urgently need an iOS version which utilizes your previous work.</li></ol><blockquote><em>60% of developers use or have tried Kotlin Multiplatform in production according to this recently published </em><a href="https://blog.jetbrains.com/kotlin/2022/06/multiplatform-survey-q3-q4-2021/"><em>survey by JetBrains</em></a><em>.</em></blockquote><h3>What are the benefits of KMM integration?</h3><p><strong>It can potentially save you up to a third of your budget.</strong> This is mainly because you develop a common code base for two platforms, Android and iOS, which can be done by a single developer.</p><p><strong>You can speed up your development by 25%</strong> and implement new features on both platforms faster and easier in the future.</p><p><strong>The projects will behave identically on these platforms</strong> without any discrepancies in minute details or complex cases.</p><p><strong>iOS and Android developers will share the context,</strong> which will allow them to understand each other’s code in any part of the business logic and to promptly improve and fix it.</p><blockquote><em>These might be the reasons why over 75% of those who have played around with Kotlin Multiplatform</em><a href="https://blog.jetbrains.com/kotlin/2022/06/multiplatform-survey-q3-q4-2021/"><em> are considering to use it for their production</em></a><em> </em><a href="https://blog.jetbrains.com/kotlin/2022/06/multiplatform-survey-q3-q4-2021/"><em>projects</em></a><em>.</em></blockquote><h3>How does KMM migration occur?</h3><p>The process of migrating to KMM depends on your current situation with the project. There are three basic scenarios. Let’s use our case studies as examples.</p><h3>Scenario 1. You have an app on both platforms and you need expert advice on developing common business logic.</h3><p>This is the type of migration we did for Profi.</p><p>Profi is an app for searching for tradespeople and professionals in different fields. The app runs on iOS and Android. The customer asked us to help with migrating its business logic from the Android app to multiplatform.</p><p><strong>The process we went through:</strong></p><ol><li>We signed an NDA.</li><li>We analyzed the project.</li><li>After that we created an individual plan for KMM integration.</li><li>The customer used our plan to move business logic to common code.</li></ol><p><strong>How long it took and how much it cost. </strong>In general, an integration using this scenario can take anywhere from two weeks to half a year depending on the project, and the cost starts at ~$6600. But in this case the customer did most of the work himself, and we only helped them plan their migration. Our part took us just a few days: in this time we analyzed what the customer had already done to create common code and prepared the plan for business logic migration.</p><p>The plan for this project included the following steps:</p><ol><li>Set up Kotlin Multiplatform support in the Gradle module.</li><li>Move platform-independent classes to <em>commonMain.</em></li><li>Replace JVM/Android libraries with their multiplatform counterparts.</li><li>Move JVM-dependent code to be changed to <em>commonMain</em>.</li><li>Move Android-dependent code to be changed to <em>commonMain</em>.</li><li>Make public interfaces of the common logic components easy-to-use on both platforms.</li></ol><p>Afterwards the Profi team was able to use our review in their work.</p><h3>Scenario 2. Your team has had some success in trying KMM, and you only need expert advice.</h3><p>This was how we worked with Footballco, an international service for watching football, which also runs on both iOS and Android.</p><p>We joined the project at the launch stage. The customer’s team first created common business logic on their own and plugged it into their Android and iOS apps, and afterwards they reached out to us for advice.</p><p><strong>The process we went through:</strong></p><ol><li>We signed an NDA.</li><li>We analyzed the project.</li><li>We have provided our recommendations and learning materials.</li><li>The customer implemented some of the recommendations and contacted us for an explanation about another part.</li><li>We went over the unclear parts during a conference call.</li></ol><p><strong>How long it took and how much it cost.</strong> Analyzing and planning usually takes two to five days, and the cost starts at $1300. The audit for this project took us three days and came out at exactly $1300.</p><p>Here are some of our recommendations for this project:</p><ol><li>Streamline the Gradle configuration using the official plugin and<a href="https://moko.icerock.dev/"> our</a> <a href="https://moko.icerock.dev/">open-source libraries</a>.</li><li>Enable project hierarchy to use IDEA hints and auto-completion features in <em>iosMain.</em></li><li>Enable <em>Kotlin/Native cache</em>, since this significantly improves debug build time.</li><li>Avoid transitive module export and enable it explicitly for the required modules and libraries to improve build time.</li><li>Move away from manually wrapping coroutines in favor of auto-generated ones to get equivalent asynchronous methods based on closures on the Swift side.</li><li>Set up object freezing right in the <em>init</em> to avoid potential errors with multi-threaded operation.</li><li>Fix the bug with the iOS app launching: one of the Gradle plugins was working incorrectly. It turned out that the Gradle task <em>podspec</em> was configured for a static framework, but the last build of the framework was dynamic. To fix this, we suggested making <em>podspec</em> generate a dummy dynamic framework, so that everything compiles nicely.</li></ol><h3>Scenario 3. You have to urgently release an iOS version for the existing Android app without interrupting the development to merge business logic of both apps afterwards.</h3><p>This is what we did for GetMega, an app for multiplayer online gaming and chatting. We based the common code on the Android version, which was already written in Kotlin, and designed it for iOS integration. This allowed the customer to get two apps with common logic without interrupting their work on the features of the Android app.</p><p><strong>The process we went through:</strong></p><ol><li>We signed an NDA.</li><li>We analyzed the project.</li><li>After that we created an individual plan for KMM integration.</li><li>Then we wrote code for the second platform with the integration in mind, so that the customer could integrate it with the Android version on their own.</li></ol><p><strong>How long it took and how much it cost.</strong> This is the most expensive and complex service, the cost can start at ~$13000. Analyzing and planning usually takes two to five days. Developing an app with a common code can take one to six months, and the integration with the original platform might require anywhere from two weeks to several months. For this project the analysis took us three days and the development was finished in three months. And the customer stayed within the budget of ~$40000.</p><blockquote><em>Which parts do developers move into the common code</em> <a href="https://blog.jetbrains.com/kotlin/2022/06/multiplatform-survey-q3-q4-2021/"><em>according to statistics</em></a><em>? Between the platforms, 76% of developers share data models, 66% share networking, and 65% share data serialization.</em></blockquote><p>We’d be happy to consult teams and businesses on mobile app development with Kotlin Multiplatform Mobile or help with development. You can contact us <a href="https://icerockdev.com/order">here</a>.</p><blockquote><a href="https://blog.jetbrains.com/kotlin/2022/06/multiplatform-survey-q3-q4-2021/"><em>And here’s a final piece of statistics for you</em></a><em>: 45% of developers have participated in more than one Kotlin Multiplatform project. Tell us in the comment section whether you are one of them ;)</em></blockquote><p>Read more about KMM <a href="https://icerockdev.com/blog/kotlin-multiplatform">in our blog</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4f27518b99a5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/faster-development-at-a-lower-cost-migration-to-kotlin-multiplatform-mobile-4f27518b99a5">Faster Development at a Lower Cost. Migration to Kotlin Multiplatform Mobile</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maximizing Mobile Code Reuse with Compose Multiplatform and MOKO Libraries]]></title>
            <link>https://medium.com/icerock/maximizing-mobile-code-reuse-with-compose-multiplatform-and-moko-libraries-140a408b452a?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/140a408b452a</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Aleksey Mikhailov]]></dc:creator>
            <pubDate>Thu, 13 Apr 2023 06:46:01 GMT</pubDate>
            <atom:updated>2023-04-13T06:46:01.182Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QPMFSd2g5KZO5FPnWjDGOQ.jpeg" /></figure><p>Mobile application development can be a complex and time-consuming process, requiring developers to write separate code for each platform they want to target. To help streamline this process, JetBrains recently <a href="https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.4.0">released</a> Compose Multiplatform, a UI framework that allows developers to write UI for Android, iOS (Alpha), Desktop, Web (Experimental) in Kotlin common code. While Compose Multiplatform is a powerful tool for creating cross-platform user interfaces, it has some limitations when it comes to handling non-UI mobile features like permissions, geolocation, and biometric authentication. To address these limitations, developers can turn to the MOKO libraries, a set of Kotlin Multiplatform Mobile (KMM) libraries that are designed to work seamlessly with Compose Multiplatform.</p><h4>What Compose Multiplatform Allows You to Do</h4><p>Compose Multiplatform provides a powerful UI framework that enables developers to create consistent and responsive user interfaces across multiple platforms using Kotlin common code. This means that developers can write a single codebase that can be used to create UIs for both Android and iOS platforms. With Compose Multiplatform, developers can use a declarative approach to build UI components, making it easier to create complex UIs with less code. Compose Multiplatform also provides support for animations, theming, and custom layouts, making it a versatile and flexible tool for creating cross-platform UIs. Starts from Compose Multiplatform 1.4.0 all material ui that was developer for Android now available for iOS too.</p><h4>What Compose Multiplatform Does Not Do</h4><p>While Compose Multiplatform is a powerful tool for creating cross-platform user interfaces, it has some limitations when it comes to handling non-UI mobile features like permissions, geolocation, and biometric authentication. For example, Compose Multiplatform does not handle Android configuration changes. Additionally, Compose Multiplatform does not provide built-in support for requesting runtime permissions or tracking geolocation on Android or iOS platforms. This is where the MOKO libraries come in.</p><h4>How MOKO Complements Compose Multiplatform</h4><p>The MOKO libraries are a set of KMM libraries that are designed to work seamlessly with Compose Multiplatform. These libraries can help developers to handle common mobile features in a cross-platform way, without having to write platform-specific code. Here are some of the key features of the MOKO libraries:</p><ul><li><a href="https://github.com/icerockdev/moko-resources">moko-resources</a>: This library provides support for managing shared resources, including images, fonts, strings, plurals, raw resources, and more. With moko-resources, developers can easily load resources from their shared code and use them in their Compose UI.</li><li><a href="https://github.com/icerockdev/moko-mvvm">moko-mvvm</a>: This library provides support for implementing the MVVM pattern and handling Android configuration changes. With moko-mvvm, developers can create ViewModel classes that can be shared across multiple platforms, enabling them to handle view logic and state management in a consistent and efficient way.</li><li><a href="https://github.com/icerockdev/moko-permissions">moko-permissions</a>: This library provides support for requesting runtime permissions on Android and iOS.</li><li><a href="https://github.com/icerockdev/moko-media">moko-media</a>: This library provides support for selecting and displaying images on Android and iOS. With moko-media, developers can easily select images from their shared code and display them in their Compose UI, or send them to a server.</li><li><a href="https://github.com/icerockdev/moko-geo">moko-geo</a>: This library provides support for tracking geolocation on Android and iOS. With moko-geo, developers can easily track the user’s location from their shared code, enabling them to create location-aware applications that work seamlessly across multiple platforms.</li><li><a href="https://github.com/icerockdev/moko-biometry">moko-biometry</a>: This library provides support for implementing biometric authentication on Android and iOS. With moko-biometry, developers can easily implement touch and face ID checks from their shared code, enabling them to create secure and efficient authentication flows across multiple platforms.</li></ul><h4>Where to Find MOKO Libraries and Compose Multiplatform Templates</h4><p>Developers interested in using Compose Multiplatform and the MOKO libraries in their own projects can find more information and resources on the official <a href="https://moko.icerock.dev/">MOKO website</a> and the <a href="https://www.jetbrains.com/lp/compose-multiplatform/">Compose Multiplatform website</a>. In addition, the <a href="https://icerockdev.com">IceRock Development</a> has configure several official Compose Multiplatform templates to work with the MOKO libraries.</p><p>Template for <a href="https://github.com/icerockdev/moko-compose-multiplatform-ios-android-template">Android and iOS applications</a> and template for <a href="https://github.com/icerockdev/moko-compose-multiplatform-template">Android, iOS, and desktop applications</a>.</p><h4>Conclusion</h4><p>Compose Multiplatform and the MOKO libraries offer developers a powerful set of tools for creating cross-platform mobile applications with maximum code reuse. With Compose Multiplatform, developers can write UI for Android and iOS in Kotlin common code, while the MOKO libraries provide support for common mobile features like permissions, geolocation, and biometric authentication. By leveraging these tools, developers can streamline their development process and create mobile applications that work seamlessly across multiple platforms, with less code and fewer bugs. If you’re interested in exploring Compose Multiplatform and the MOKO libraries for your own projects, be sure to check out the resources available on the MOKO website and the Compose Multiplatform documentation, and consider using one of the pre-configured templates to get started quickly and easily.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=140a408b452a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/maximizing-mobile-code-reuse-with-compose-multiplatform-and-moko-libraries-140a408b452a">Maximizing Mobile Code Reuse with Compose Multiplatform and MOKO Libraries</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MOKO resources 0.21 with Compose Multiplatform support]]></title>
            <link>https://medium.com/icerock/moko-resources-0-21-with-compose-multiplatform-support-462d8b11116b?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/462d8b11116b</guid>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Aleksey Mikhailov]]></dc:creator>
            <pubDate>Sat, 25 Mar 2023 15:49:09 GMT</pubDate>
            <atom:updated>2023-03-25T15:48:56.616Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pjCSl6qVLREOtW28APY8lA.png" /></figure><p><a href="https://github.com/icerockdev/moko-resources">MOKO resources</a> is a multiplatform Kotlin library that provides a convenient way to access resources like images, colors, strings, and fonts in your Kotlin Multiplatform projects. With the release of version 0.21.0, MOKO resources now includes support for <a href="https://github.com/JetBrains/compose-multiplatform">Compose Multiplatform</a>, allowing developers to easily access resources across all supported platforms.</p><p>One of the key benefits of using MOKO resources with Compose Multiplatform is the ability to easily use fonts, colors, and images in your UI. With the fontFamilyResource, colorResource, and painterResource functions provided by MOKO resources, it&#39;s easy to reference resources from your shared code.</p><p>In this article, we’ll take a closer look at how to use MOKO resources with Compose Multiplatform, and how it can help you build better multiplatform applications.</p><h3>Usage</h3><p>MOKO resources provides several functions for accessing resources like images, colors, strings, and fonts. Here’s a brief overview of how to use each of these functions:</p><h3>ImageResource</h3><p>To display an image in your Compose Multiplatform UI, use the painterResource function with the ID of the image resource you want to use:</p><pre>Image(<br>    painter = painterResource(MR.images.logo),<br>    contentDescription = null<br>)</pre><h3>FontResource</h3><p>To use a custom font in your Compose Multiplatform UI, use the fontFamilyResource function:</p><pre>Text(<br>    text = &quot;Hello, World!&quot;, <br>    fontFamily = fontFamilyResource(MR.fonts.custom_font)<br>)</pre><h3>StringResource</h3><p>To display a localized string in your Compose Multiplatform UI, use the stringResource function with the ID of the string resource you want to use:</p><pre>Text(text = stringResource(MR.strings.hello_world))</pre><h3>ColorResource</h3><p>To set the color of a Compose Multiplatform component, use the ColorResource class with the ID of the color resource you want to use:</p><pre>Text(<br>    text = &quot;Hello, World!&quot;,<br>    color = colorResource(MR.colors.background)<br>)</pre><h3>AssetResource</h3><p>To load the contents of an asset file in your Compose Multiplatform UI, use the AssetResource and extension function:</p><pre>val assetContent: String? by MR.assets.some_asset.readTextAsState() <br>Text(text = assetContent.orEmpty())</pre><h3>FileResource</h3><p>To load the contents of a file in your Compose Multiplatform UI, use the FileResource and extension function:</p><pre>val fileContent: String? by MR.files.some_file.readTextAsState() <br>Text(text = fileContent.orEmpty())</pre><h3>Compose Multiplatform Screen</h3><p>To show how to use MOKO resources with Compose Multiplatform, let’s build a simple screen that displays an image, a text input, and some localized text. Here’s the code:</p><pre>@Composable<br>internal fun App() {<br>    MaterialTheme(<br>        colors = if (isSystemInDarkTheme()) darkColors() else lightColors()<br>    ) {<br>        Column(<br>            modifier = Modifier.fillMaxSize()<br>                .background(color = MaterialTheme.colors.background)<br>                .padding(16.dp),<br>            horizontalAlignment = Alignment.CenterHorizontally<br>        ) {<br>            Image(<br>                painter = painterResource(MR.images.moko_logo),<br>                contentDescription = null<br>            )<br><br>            var text: String by remember { mutableStateOf(&quot;&quot;) }<br><br>            OutlinedTextField(<br>                modifier = Modifier.fillMaxWidth()<br>                    .padding(top = 16.dp),<br>                value = text,<br>                colors = TextFieldDefaults.outlinedTextFieldColors(<br>                    textColor = MaterialTheme.colors.onBackground<br>                ),<br>                onValueChange = { text = it }<br>            )<br><br>            val counter: Int = text.length<br>            Text(<br>                modifier = Modifier.fillMaxWidth()<br>                    .padding(vertical = 8.dp),<br>                text = stringResource(MR.plurals.chars_count, counter, counter),<br>                color = colorResource(MR.colors.textColor),<br>                fontFamily = fontFamilyResource(MR.fonts.cormorant.italic)<br>            )<br><br>            Button(onClick = { text = &quot;Hello, ${getPlatformName()}&quot; }) {<br>                Text(text = stringResource(MR.strings.hello_world))<br>            }<br><br>            val fileContent: String? by MR.files.some_file.readTextAsState()<br>            Text(<br>                modifier = Modifier.padding(top = 16.dp),<br>                text = fileContent.orEmpty(),<br>                color = MaterialTheme.colors.onBackground<br>            )<br><br>            val assetContent: String? by MR.assets.some_asset.readTextAsState()<br>            Text(<br>                modifier = Modifier.padding(top = 16.dp),<br>                text = assetContent.orEmpty(),<br>                color = MaterialTheme.colors.onBackground<br>            )<br>        }<br>    }<br>}</pre><p>With this simple example, we can see how easy it is to use MOKO resources with Compose Multiplatform to build UIs that look and feel great across all supported platforms.</p><p>Full code you can see in <a href="https://github.com/icerockdev/moko-resources/tree/master/samples/compose-resources-gallery">moko-resources GitHub repository</a>.</p><h3>Conclusions</h3><p>MOKO resources is a powerful library that provides a convenient way to access resources like images, colors, strings, and fonts in your Kotlin Multiplatform projects. With the release of version 0.21.0, MOKO resources now includes support for Compose Multiplatform, making it even easier to build great multiplatform applications.</p><p>Using MOKO resources with Compose Multiplatform allows developers to easily access resources across all supported platforms, and to create UIs that look and feel great everywhere. With the fontFamilyResource, colorResource, and painterResource functions provided by MOKO resources, it&#39;s easy to reference resources from your shared code, and to create UIs that are consistent across all platforms.</p><p>In this article, we provided some examples of how to use MOKO resources with Compose Multiplatform, including how to load images, fonts, colors, strings, and assets in your UIs. We also showed how to use MOKO resources to build a simple screen that displays an image, a text input, and some localized text.</p><p>In summary, if you’re building Kotlin Multiplatform applications with Compose Multiplatform, MOKO resources is an essential library to have in your toolkit. Its support for Compose Multiplatform makes it easy to create consistent and great-looking UIs across all supported platforms, and its convenient resource-loading functions make it easy to access resources from your shared code.</p><h3>Thanks</h3><p>I want to say thanks for great community members, that helps us to create new release.</p><p><a href="https://github.com/kevincianfarini">@kevincianfarini</a> , <a href="https://github.com/jittya">@jittya</a> , <a href="https://github.com/zacharee">@zacharee</a> , <a href="https://github.com/Cilestal">@Cilestal</a> , <a href="https://github.com/InsanusMokrassar">@InsanusMokrassar</a> , <a href="https://github.com/wakaztahir">@wakaztahir</a> , <a href="https://github.com/PaulWoitaschek">@PaulWoitaschek</a></p><p>And also, thanks to <a href="https://openai.com/">OpenAI</a> for <a href="https://chat.openai.com/">ChatGPT</a>. This article was written with great help of ChatGPT .</p><h3>Links</h3><ul><li><a href="https://github.com/icerockdev/moko-resources">https://github.com/icerockdev/moko-resources</a></li><li><a href="https://github.com/icerockdev/moko-resources/tree/master/samples/compose-resources-gallery">https://github.com/icerockdev/moko-resources/tree/master/samples/compose-resources-gallery</a></li><li><a href="https://github.com/JetBrains/compose-multiplatform">https://github.com/JetBrains/compose-multiplatform</a></li><li><a href="https://github.com/icerockdev/moko-resources/releases/tag/release%2F0.21.0">https://github.com/icerockdev/moko-resources/releases/tag/release%2F0.21.0</a></li><li><a href="https://moko.icerock.dev">https://moko.icerock.dev</a></li><li><a href="https://www.jetbrains.com/lp/compose-mpp/">https://www.jetbrains.com/lp/compose-mpp/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=462d8b11116b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/moko-resources-0-21-with-compose-multiplatform-support-462d8b11116b">MOKO resources 0.21 with Compose Multiplatform support</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use Kotlin Multiplatform ViewModel in SwiftUI and Jetpack Compose]]></title>
            <link>https://medium.com/icerock/how-to-use-kotlin-multiplatform-viewmodel-in-swiftui-and-jetpack-compose-8158e98c091d?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/8158e98c091d</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Aleksey Mikhailov]]></dc:creator>
            <pubDate>Sat, 30 Apr 2022 12:34:48 GMT</pubDate>
            <atom:updated>2022-04-30T12:34:48.571Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*bneteboqXwn901dACrWX9A.png" /></figure><p>We at <a href="https://icerock.dev/">IceRock Development</a> have been using the MVVM approach for many years, and the last 4 years our ViewModel are shared in the common code. We do it by using our library <a href="https://github.com/icerockdev/moko-mvvm">moko-mvvm</a>. In the last year, we have been actively moving to using Jetpack Compose and SwiftUI to build UI in our projects. And it require MOKO MVVM improvements to make it more comfortable for developers on both platforms to work with this approach.</p><p>On April 30, 2022, <a href="https://github.com/icerockdev/moko-mvvm/releases/tag/release%2F0.13.0">new version of MOKO MVVM — 0.13.0</a> was released. This version has full support for Jetpack Compose and SwiftUI. Let’s take an example of how you can use ViewModel from common code with these frameworks.</p><p>The example will be simple — an application with an authorization screen. Two input fields — login and password, button Log in and a message about a successful login after a second of waiting (while waiting, we turn the progress bar).</p><h3>Create a project</h3><p>The first step is simple — take Android Studio, install <a href="https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile">Kotlin Multiplatform Mobile IDE plugin</a>, if not already installed. Create a project according to the template “Kotlin Multiplatform App” using CocoaPods integration (it’s more convenient with them, plus we need it to connect an additional CocoaPod later).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mokkDV7InfwQNav2.png" /></figure><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/ee223a80e17616e622d135c0651ab454eabfad7a">git commit</a></p><h3>Login screen on Android with Jetpack Compose</h3><p>The app template uses the standard Android View approach, so we need to enable Jetpack Compose before implementation of UI.</p><p>Enable Compose support in androidApp/build.gradle.kts:</p><pre>val composeVersion = &quot;1.1.1&quot;</pre><pre>android {<br>    // ...</pre><pre>    buildFeatures {<br>        compose=true<br>    }<br>    composeOptions {<br>        kotlinCompilerExtensionVersion = composeVersion<br>    }<br>}</pre><p>And we add the dependencies we need, removing the old unnecessary ones (related to the usual approach with view):</p><pre>dependencies {<br>    implementation(project(&quot;:shared&quot;))</pre><pre>    implementation(&quot;androidx.compose.foundation:foundation:$composeVersion&quot;)<br>    implementation(&quot;androidx.compose.runtime:runtime:$composeVersion&quot;)<br>    // UI<br>    implementation(&quot;androidx.compose.ui:ui:$composeVersion&quot;)<br>    implementation(&quot;androidx.compose.ui:ui-tooling:$composeVersion&quot;)<br>    // material design<br>    implementation(&quot;androidx.compose.material:material:$composeVersion&quot;)<br>    implementation(&quot;androidx.compose.material:material-icons-core:$composeVersion&quot;)<br>    // Activity<br>    implementation(&quot;androidx.activity:activity-compose:1.4.0&quot;)<br>    implementation(&quot;androidx.appcompat:appcompat:1.4.1&quot;)<br>}</pre><p>When running Gradle Sync, we get a message about the version incompatibility between Jetpack Compose and Kotlin. This is due to the fact that Compose uses a compiler plugin for Kotlin, and the compiler plugin APIs are not yet stabilized. Therefore, we need to install the version of Kotlin that supports the Compose version we are using is 1.6.10.</p><p>Next, it remains to implement the authorization screen, I immediately give the finished code:</p><pre>@Composable<br>fun LoginScreen() {<br>    val context: Context = LocalContext.current<br>    val coroutineScope: CoroutineScope = rememberCoroutineScope()</pre><pre>    var login: String by remember { mutableStateOf(&quot;&quot;) }<br>    var password: String by remember { mutableStateOf(&quot;&quot;) }<br>    var isLoading: Boolean by remember { mutableStateOf(false) }</pre><pre>    val isLoginButtonEnabled: Boolean = login.isNotBlank() &amp;&amp; password.isNotBlank() &amp;&amp; !isLoading</pre><pre>    Column(<br>        modifier = Modifier.padding(16.dp),<br>        horizontalAlignment = Alignment.CenterHorizontally<br>    ) {<br>        TextField(<br>            modifier = Modifier.fillMaxWidth(),<br>            value = login,<br>            enabled = !isLoading,<br>            label = { Text(text = &quot;Login&quot;) },<br>            onValueChange = { login = it }<br>        )<br>        Spacer(modifier = Modifier.height(8.dp))<br>        TextField(<br>            modifier = Modifier.fillMaxWidth(),<br>            value = password,<br>            enabled = !isLoading,<br>            label = { Text(text = &quot;Password&quot;) },<br>            visualTransformation = PasswordVisualTransformation(),<br>            onValueChange = { password = it }<br>        )<br>        Spacer(modifier = Modifier.height(8.dp))<br>        Button(<br>            modifier = Modifier<br>                .fillMaxWidth()<br>                .height(48.dp),<br>            enabled = isLoginButtonEnabled,<br>            onClick = {<br>                coroutineScope.launch {<br>                    isLoading = true<br>                    delay(1000)<br>                    isLoading = false<br>                    Toast.makeText(context, &quot;login success!&quot;, Toast.LENGTH_SHORT).show()<br>                }<br>            }<br>        ) {<br>            if (isLoading) CircularProgressIndicator(modifier = Modifier.size<br>(24.dp))<br>            else Text(text = &quot;login&quot;)<br>        }<br>    }<br>}</pre><p>And here is our android app with authorization screen ready and functioning as required, but without common code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/479/0*ve7wi5Eb4m-DaO8T.gif" /></figure><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/69cf1904cd16f34b5bc646cdcacda3b72c8b58cf">git commit</a></p><h3>Authorization screen in iOS with SwiftUI</h3><p>Let’s make the same screen in SwiftUI. The template has already created a SwiftUI app, so it’s easy enough for us to write screen code. We get the following code:</p><pre>struct LoginScreen: View {<br>    @State private var login: String = &quot;&quot;<br>    @State private var password: String = &quot;&quot;<br>    @State private var isLoading: Bool = false<br>    @State private var isSuccessfulAlertShowed: Bool = false<br>    <br>    private var isButtonEnabled: Bool {<br>        get {<br>            !isLoading &amp;&amp; !login.isEmpty &amp;&amp; !password.isEmpty<br>        }<br>    }<br>    <br>    var body: someView {<br>        Group {<br>            VStack(spacing: 8.0) {<br>                TextField(&quot;Login&quot;, text: $login)<br>                    .textFieldStyle(.roundedBorder)<br>                    .disabled(isLoading)<br>                <br>                SecureField(&quot;Password&quot;, text: $password)<br>                    .textFieldStyle(.roundedBorder)<br>                    .disabled(isLoading)<br>                <br>                Button(<br>                    action: {<br>                        isLoading = true<br>                        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {<br>                            isLoading = false<br>                            isSuccessfulAlertShowed = true<br>                        }<br>                    }, label: {<br>                        if isLoading {<br>                            ProgressView()<br>                        } else {<br>                            Text(&quot;login&quot;)<br>                        }<br>                    }<br>                ).disabled(!isButtonEnabled)<br>            }.padding()<br>        }.alert(<br>            &quot;Login successful&quot;,<br>            isPresented: $isSuccessfulAlertShowed<br>        ) {<br>            Button(&quot;Close&quot;, action: { isSuccessfulAlertShowed = false })<br>        }<br>    }<br>}</pre><p>The logic of work is completely identical to the Android version and also does not use any common logic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/0*3JtdjQEPm9lLEEe4.gif" /></figure><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/760622ab392b1e723e4bb508d8f5c8b97b9ca5a7">git commit</a></p><h3>Implement a common ViewModel</h3><p>All preparatory steps are completed. It’s time to move the authorization screen logic out of the platforms in common code.</p><p>The first thing we will do for this is to connect the moko-mvvm dependency to the common module and add it to export list for iOS framework (so that in Swift we can see all public classes and methods of this libraries).</p><pre>val mokoMvvmVersion = &quot;0.13.0&quot;</pre><pre>kotlin {<br>    // ...</pre><pre>    cocoapods {<br>        // ...<br>        <br>        framework {<br>            baseName = &quot;MultiPlatformLibrary&quot;</pre><pre>            export(&quot;dev.icerock.moko:mvvm-core:$mokoMvvmVersion&quot;)<br>            export(&quot;dev.icerock.moko:mvvm-flow:$mokoMvvmVersion&quot;)<br>        }<br>    }</pre><pre>    sourceSets {<br>        val commonMain by getting {<br>            dependencies {<br>                api(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1-native-mt&quot;)</pre><pre>                api(&quot;dev.icerock.moko:mvvm-core:$mokoMvvmVersion&quot;)<br>                api(&quot;dev.icerock.moko:mvvm-flow:$mokoMvvmVersion&quot;)<br>            }<br>        }<br>        // ...<br>        val androidMain by getting {<br>            dependencies {<br>                api(&quot;dev.icerock.moko:mvvm-flow-compose:$mokoMvvmVersion&quot;)<br>            }<br>        }<br>        // ...<br>    }<br>}</pre><p>We also changed the baseName of the iOS Framework to MultiPlatformLibrary. This is an important change, which we will not be able to connect CocoaPod with Kotlin and SwiftUI integration functions in the future.</p><p>It remains to write the LoginViewModel itself. Here is the code:</p><pre>class LoginViewModel : ViewModel() {<br>    val login: MutableStateFlow&lt;String&gt; = MutableStateFlow(&quot;&quot;)<br>    val password: MutableStateFlow&lt;String&gt; = MutableStateFlow(&quot;&quot;)</pre><pre>    private val _isLoading: MutableStateFlow&lt;Boolean&gt; = MutableStateFlow(false)<br>    val isLoading: StateFlow&lt;Boolean&gt; = _isLoading</pre><pre>    val isButtonEnabled: StateFlow&lt;Boolean&gt; =<br>        combine(isLoading, login, password) { isLoading, login, password -&gt;<br>            isLoading.not() &amp;&amp; login.isNotBlank() &amp;&amp; password.isNotBlank()<br>        }.stateIn(viewModelScope, SharingStarted.Eagerly, false)</pre><pre>    private val _actions = Channel&lt;Action&gt;()<br>    val actions: Flow&lt;Action&gt; get() = _actions.receiveAsFlow()</pre><pre>    fun onLoginPressed() {<br>        _isLoading.value = true<br>        viewModelScope.launch {<br>            delay(1000)<br>            _isLoading.value = false<br>            _actions.send(Action.LoginSuccess)<br>        }<br>    }</pre><pre>    sealed interface Action {<br>        object LoginSuccess : Action<br>    }<br>}</pre><p>For input fields that can be changed by the user, we used MutableStateFlow from kotlinx-coroutines (but you can also use MutableLiveData from moko-mvvm-livedata). For properties that the UI should keep track of but should not change - use StateFlow. And to notify about the need to do something (show a success message or to go to another screen) we have created a Channel which is exposed on the UI as a Flow. All available actions we combine under a single sealed interface Action so that it is known exactly what actions can tell the given ViewModel.</p><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/d628fb60fedeeb0d259508aa09d3a98ebbc9651c">git commit</a></p><h3>Connect the common ViewModel to Android</h3><p>On Android, to get our ViewModel from ViewModelStorage (so that when the screen rotates we received the same ViewModel) we need to include a special dependency in androidApp/build.gradle.kts:</p><pre>dependencies {<br>    // ...<br>    implementation(&quot;androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1&quot;)<br>}</pre><p>Next, add LoginViewModel to our screen arguments:</p><pre>@Composable<br>fun LoginScreen(<br>    viewModel: LoginViewModel = viewModel()<br>)</pre><p>Let’s replace the local state of the screen with getting the state from the LoginViewModel:</p><pre>val login: String by viewModel.login.collectAsState()<br>val password: String by viewModel.password.collectAsState()<br>val isLoading: Boolean by viewModel.isLoading.collectAsState()<br>val isLoginButtonEnabled: Boolean by viewModel.isButtonEnabled.collectAsState()</pre><p>Subscribe to receive actions from the ViewModel using observeAsAction from moko-mvvm:</p><pre>viewModel.actions.observeAsActions { action -&gt;<br>    when (action) {<br>        LoginViewModel.Action.LoginSuccess -&gt;<br>            Toast.makeText(context, &quot;login success!&quot;, Toast.LENGTH_SHORT).show()<br>    }<br>}</pre><p>Let’s change the input handler of TextFields from local state to writing to ViewModel:</p><pre>TextField(<br>    // ...<br>    onValueChange = { viewModel.login.value = it }<br>)</pre><p>And call the button click handler:</p><pre>Button(<br>    // ...<br>    onClick = viewModel::onLoginPressed<br>) {<br>    // ...<br>}</pre><p>We run the application and see that everything works exactly the same as it worked before the common code, but now all screen logic is controlled by a common ViewModel.</p><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/a93b9a3b6f1e413bebbba3a30bc5a198ebbf4e84">git commit</a></p><h3>Connect the shared ViewModel to iOS</h3><p>To connect LoginViewModel to SwiftUI, we need Swift add-ons from MOKO MVVM. They connect via CocoaPods:</p><pre>pod &#39;mokoMvvmFlowSwiftUI&#39;, :podspec =&gt; &#39;https://raw.githubusercontent.com/icerockdev/moko-mvvm/release/0.13.0/mokoMvvmFlowSwiftUI.podspec&#39;</pre><p>And also, in the LoginViewModel itself, you need to make changes - from the side of Swift MutableStateFlow, StateFlow, Flow will lose their generic type since they are interfaces. So that the generic is not lost you need to use classes. MOKO MVVM provides special CMutableStateFlow, CStateFlow and CFlow classes to store the generic type in iOS. We bring the types with the following change:</p><pre>class LoginViewModel : ViewModel() {<br>    val login: CMutableStateFlow&lt;String&gt; = MutableStateFlow(&quot;&quot;).cMutableStateFlow()<br>    val password: CMutableStateFlow&lt;String&gt; = MutableStateFlow(&quot;&quot;).cMutableStateFlow()</pre><pre>    // ...<br>    val isLoading: CStateFlow&lt;Boolean&gt; = _isLoading.cStateFlow()</pre><pre>    val isButtonEnabled: CStateFlow&lt;Boolean&gt; =<br>        // ...<br>        .cStateFlow()<br>    <br>    // ...<br>    val actions: CFlow&lt;Action&gt; get() = _actions.receiveAsFlow().cFlow()</pre><pre>    // ...<br>}</pre><p>Now we can move on to the Swift code. To integrate, we make the following change:</p><pre>import MultiPlatformLibrary<br>import mokoMvvmFlowSwiftUI<br>import Combine</pre><pre>struct LoginScreen: View {<br>    @ObservedObject var viewModel: LoginViewModel = LoginViewModel()<br>    @State private var isSuccessfulAlertShowed: Bool = false<br>    <br>    // ...<br>}</pre><p>We add viewModel to View as @ObservedObject, just like we do with Swift versions ViewModel, but in this case, due to the use of mokoMvvmFlowSwiftUI we can immediately pass Kotlin class LoginViewModel.</p><p>Next, change the binding of the fields:</p><pre>TextField(&quot;Login&quot;, text: viewModel.binding(\.login))<br>    .textFieldStyle(.roundedBorder)<br>    .disabled(viewModel.state(\.isLoading))</pre><p>mokoMvvmFlowSwiftUI provides special extension functions to ViewModel:</p><ul><li>binding returns a Binding structure, for the possibility of changing data from the UI side</li><li>state returns a value that will be automatically updated when StateFlow returns new data</li></ul><p>Similarly, we replace other places where the local state is used and subscribe to actions:</p><pre>.onReceive(createPublisher(viewModel.actions)) { action in<br>    let actionKs = LoginViewModelActionKs(action)<br>    switch(actionKs) {<br>    case .loginSuccess:<br>        isSuccessfulAlertShowed = true<br>        break<br>    }<br>}</pre><p>The createPublisher function is also provided from mokoMvvmFlowSwiftUI and allows you to transform CFlow in AnyPublisher from Combine. For reliable processing actions we use <a href="https://github.com/icerockdev/moko-kswift">moko-kswift</a>. This is a gradle plugin that automatically generates swift code based on Kotlin. In this case, Swift was generated enum LoginViewModelActionKs from sealed interface LoginViewModel.Action. Using automatically generated enum we get a guarantee that the cases in enum and sealed interface match, so now we can rely on exhaustive switch logic. You can read more about MOKO KSwift <a href="https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d">in the article</a>.</p><p>As a result, we got a SwiftUI screen that is controlled from a common code using the MVVM approach.</p><p><a href="https://github.com/Alex009/moko-mvvm-compose-swiftui/commit/5e260fbf9e4957c6fa5d1679a4282691d37da96a">git commit</a></p><h3>Conclusion</h3><p>In development with Kotlin Multiplatform Mobile, we consider it important to strive to provide a convenient toolkit for both platforms — both Android and iOS developers should comfortably develop and the use of any approach in the common code should not force the developers of one of the platforms do extra work. By developing our <a href="https://moko.icerock.dev/">MOKO</a> libraries and tools, we strive to simplify the work of developers for both Android and iOS. SwiftUI and MOKO MVVM integration required a lot of experimentation, but the final result looks comfortable to use.</p><p>You can try the project created in this article yourself, <a href="https://github.com/Alex009/moko-mvvm-compose-swiftui">on GitHub</a>.</p><p>We can also <a href="https://icerockdev.com/directions/pages/kmm/">help</a> and development teams, who need development assistance or advice on the topic of Kotlin Multiplatform Mobile.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8158e98c091d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/how-to-use-kotlin-multiplatform-viewmodel-in-swiftui-and-jetpack-compose-8158e98c091d">How to use Kotlin Multiplatform ViewModel in SwiftUI and Jetpack Compose</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to implement Swift-friendly API with Kotlin Multiplatform Mobile]]></title>
            <link>https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/e68521a63b6d</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[kotlin-native]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[Aleksey Mikhailov]]></dc:creator>
            <pubDate>Sat, 07 Aug 2021 16:49:33 GMT</pubDate>
            <atom:updated>2021-08-11T13:21:22.591Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*rrWXioDz85DU_HNaBLSKZA.png" /></figure><p><a href="https://kotlinlang.org/lp/mobile/">Kotlin Multiplatform Mobile</a> allows you to compile Kotlin code into native libraries for Android and iOS. If in the case of Android the library obtained from Kotlin will be integrated with an application written in Kotlin, then for iOS the integration will be with Swift. There is a loss of usability at the junction of Kotlin and Swift, due to the difference in languages. This is mainly because the Kotlin/Native compiler (which compiles Kotlin in the iOS framework and is part of the Kotlin Multiplatform) generates the public API of the framework in ObjectiveC. We access Kotlin from Swift through this generated ObjectiveC API, since Swift interacts with ObjectiveC. Further, I will show examples of API waste at the Kotlin-Swift junction and a tool that allows you to get a more convenient API for usage from Swift.</p><p>Let’s look at the example of using a sealed interface in Kotlin:</p><pre>sealed interface UIState&lt;out T&gt; {<br>    object Loading : UIState&lt;Nothing&gt;<br>    object Empty : UIState&lt;Nothing&gt;<br>    data class Data&lt;T&gt;(val value: T) : UIState&lt;T&gt;<br>    data class Error(val throwable: Throwable) : UIState&lt;Nothing&gt;<br>}</pre><p>This is a convenient construct to describe states, which is actively used in the Kotlin code. Let’s see how it looks from the Swift side.</p><pre>public protocol UIState { }</pre><pre>public class UIStateLoading : KotlinBase, UIState { }</pre><pre>public class UIStateEmpty : KotlinBase, UIState { }</pre><pre>public class UIStateData&lt;T&gt; : KotlinBase, UIState where T : AnyObject {<br>    open var value: T? { get }<br>}</pre><pre>public class UIStateError : KotlinBase, UIState {<br>    open var throwable: KotlinThrowable { get }<br>}</pre><p>From the Swift side Kotlin’s sealed interface looks like a set of classes with a common protocol. Of course, in this case, one cannot hope to check the completeness of the <em>switch</em> implementation, since it is not an <em>enum</em>. For developers familiar with Swift, the <em>enum</em> is considered a more correct analog of the sealed interface, for example:</p><pre>enum UIState&lt;T&gt; {<br>  case loading<br>  case empty<br>  case data(T)<br>  case error(Error)<br>}</pre><p>We can write such an <em>enum</em> from the Swift side and convert the resulting Kotlin <em>UIState</em> into our Swift <em>enum</em>, but what if there are many such sealed interfaces? The MVI approach is quite common in which the screen state and events are described exactly by the sealed class/interface. Writing an analogue in Swift for each such case is expensive. Moreover, we run the risk of class desynchronization in Kotlin and enum in Swift.</p><p>To solve this problem, we at <a href="https://icerockdev.com/">IceRock</a> made a special gradle plugin — <a href="https://github.com/icerockdev/moko-kswift">MOKO KSwift</a>. This is a gradle plugin that reads all the klibs used when compiling the iOS framework. Klib is the library format into which Kotlin/Native compiles everything before building the final binaries for a specific target. A lot of metadata is available inside klib, which gives complete information about the entire public Kotlin api, without loss of information. Our plugin analyzes all klibs that are specified in the export for the iOS framework (that is, those whose API will be included in the header of the framework), and, based on a complete understanding of the Kotlin code, it generates Swift code, in addition to the one Kotlin. For our <em>UIState</em> example, the plugin automatically generates the following construction:</p><pre>public enum UIStateKs&lt;T : AnyObject&gt; {<br>  case loading<br>  case empty<br>  case data(UIStateData&lt;T&gt;)<br>  case error(UIStateError)</pre><pre>public init(_ obj: UIState) {<br>    if obj is MultiPlatformLibrary.UIStateLoading {<br>      self = .loading<br>    } else if obj is MultiPlatformLibrary.UIStateEmpty {<br>      self = .empty<br>    } else if let obj = obj as? MultiPlatformLibrary.UIStateData&lt;T&gt; {<br>      self = .data(obj)<br>    } else if let obj = obj as? MultiPlatformLibrary.UIStateError {<br>      self = .error(obj)<br>    } else {<br>      fatalError(&quot;UIStateKs not syncronized with UIState class&quot;)<br>    }<br>  }<br>}</pre><p>We automatically get a swift enum that is guaranteed to match the sealed interface from Kotlin. This enum can be created by passing in the <em>UIState</em> object that we get from Kotlin. And this enum has access to classes from Kotlin to get all the information you need. Since this code is fully generated automatically at each compilation, we avoid the risks associated with the human factor — the machine cannot forget to update the code in Swift after changes occured in Kotlin.</p><p>Let’s move on to the next example. In <a href="https://github.com/icerockdev/moko-mvvm">MOKO mvvm</a> (our port of Android architecture components with Android in Kotlin Multiplatform Mobile) to bind <em>LiveData</em> to UI elements, we have implemented a set of extension functions for iOS, for example:</p><pre>fun UILabel.bindText(<br>    liveData: LiveData&lt;String&gt;<br>): Closeable</pre><p>After compilation in the iOS framework, we were disappointed, because Kotlin/Native does not add extensions to platform classes:</p><pre>public class UILabelBindingKt : KotlinBase {<br>    open class func bindText(_ receiver: UILabel, liveData: LiveData&lt;NSString&gt;) -&gt; Closeable<br>}</pre><p>In use, instead of the convenient API <em>label.bindText(myLiveData)</em>, <em>UILabelBindingKt.bindText(label, myLiveData)</em> is required.</p><p>This problem can also be solved by <a href="https://github.com/icerockdev/moko-kswift">MOKO KSwift</a>, since it has complete knowledge of the entire public interface of Kotlin libraries. As a result, the following function is generated:</p><pre>public extension UIKit.UILabel {<br>  public func bindText(liveData: LiveData&lt;NSString&gt;) -&gt; Closeable {<br>    return UILabelBindingKt.bindText(self, liveData: liveData)<br>  }<br>}</pre><p>Two generators are available currently out of the box in the KSwift plugin — <em>SealedToSwiftEnumFeature</em> (for generating Swift enum) and <em>PlatformExtensionFunctionsFeature</em> (for generating extensions to platform classes), however the plugin itself has an extensible API. You can implement the generation of the needed Swift code in addition to your Kotlin code without making changes directly to the plugin — only in your gradle project. By connecting the plugin as a dependency to <em>buildSrc</em>, you can write your own generator, for example:</p><pre>import dev.icerock.moko.kswift.plugin.context.ClassContext<br>import dev.icerock.moko.kswift.plugin.feature.ProcessorContext<br>import dev.icerock.moko.kswift.plugin.feature.ProcessorFeature<br>import io.outfoxx.swiftpoet.DeclaredTypeName<br>import io.outfoxx.swiftpoet.ExtensionSpec<br>import io.outfoxx.swiftpoet.FileSpec</pre><pre>class MyKSwiftGenerator(filter: Filter&lt;ClassContext&gt;) : ProcessorFeature&lt;ClassContext&gt;(filter) {<br>    override fun doProcess(featureContext: ClassContext, processorContext: ProcessorContext) {<br>        val fileSpec: FileSpec.Builder = processorContext.fileSpecBuilder<br>        val frameworkName: String = processorContext.framework.baseName</pre><pre>val classSimpleName = featureContext.clazz.name.substringAfterLast(&#39;/&#39;)</pre><pre>fileSpec.addExtension(<br>            ExtensionSpec<br>                .builder(<br>                    DeclaredTypeName.typeName(&quot;$frameworkName.$classSimpleName&quot;)<br>                )<br>                .build()<br>        )<br>    }</pre><pre>class Config(<br>        var filter: Filter&lt;ClassContext&gt; = Filter.Exclude(emptySet())<br>    )</pre><pre>companion object : Factory&lt;ClassContext, MyKSwiftGenerator, Config&gt; {<br>        override fun create(block: Config.() -&gt; Unit): MyKSwiftGenerator {<br>            val config = Config().apply(block)<br>            return MyKSwiftGenerator(config.filter)<br>        }<br>    }<br>}</pre><p>In the above example, we include the analysis of Kotlin classes (<em>ClassContext</em>) and generate an extension in Swift for each of the Kotlin classes.<br>In the <em>Context</em> classes, all the information from the klib metadata is available, and metadata contains all the information about classes, methods, packages, etc. To the same extent as compiler plugins, but read-only (while compiler plugins allow changing the code at compile time).</p><p>The plugin is a quite new solution for now and may not work correctly in some cases, which should definitely be reported in the <a href="https://github.com/icerockdev/moko-kswift/issues">issue at GitHub</a>.</p><p>To preserve the ability to use the plugin and in cases where incorrect code is generated, the ability to filter the entities that used to generation has been added. For example, to exclude the class <em>UIState</em> from generation, you need to write in gradle:</p><pre>kswift {<br>    install(dev.icerock.moko.kswift.plugin.feature.SealedToSwiftEnumFeature) {<br>        filter = excludeFilter(&quot;ClassContext/moko-kswift.sample:mpp-library-pods/com/icerockdev/library/UIState&quot;)<br>    }<br>}</pre><p>Filtering by processed libraries and the ability to enable the <em>includeFilter</em> mode (so that generation occurs only for specified entities) is also available.</p><p>If you are using Kotlin Multiplatform Mobile technology, I recommend that you try the plugin on your project (and give feedback on <a href="https://github.com/icerockdev/moko-kswift">github</a>) — the work of iOS developers will become better when they get a Swift-friendly API for working with the Kotlin module. And, if possible, share your generator options also on <a href="https://github.com/icerockdev/moko-kswift">github</a> — the more API improvements the plugin supports out of the box, the easier it will be for everyone.</p><p>Special thanks to <strong>Svyatoslav Scherbina from JetBrains</strong> for a tip about using klib metadata.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e68521a63b6d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d">How to implement Swift-friendly API with Kotlin Multiplatform Mobile</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How We integrated Kotlin Multiplatform Into Profi]]></title>
            <link>https://medium.com/icerock/how-we-integrated-kotlin-multiplatform-into-profi-c497059f2f48?source=rss----b74ae24564e---4</link>
            <guid isPermaLink="false">https://medium.com/p/c497059f2f48</guid>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[mobile-apps]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Mon, 28 Jun 2021 07:43:15 GMT</pubDate>
            <atom:updated>2023-06-05T10:19:55.115Z</atom:updated>
            <content:encoded><![CDATA[<h3>How We Integrated Kotlin Multiplatform Into Profi</h3><p><em>Hi, guys, it’s </em><a href="https://icerockdev.com/"><em>IceRock team</em></a><em>. So great to get feedback from customers. This time guys from Profi.ru wrote an article. Hope their experience will be useful and inspiring for you.</em></p><p>Hello! My name is Mikhail Ignatov, and I am a team lead at Profi. My team is responsible for client-side mobile apps for Android and iOS. We have been using Kotlin Multiplatform in production since 2019. Let me tell you about why we chose this particular technology, how we integrated it, the key stages that we went through during the process, and the conclusions we reached in the end.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4XIVtg9bHSbGyP8tgjkYgA.png" /></figure><h3>Kotlin Multiplatform</h3><p>Kotlin Multiplatform allows users to run the same Kotlin code <a href="https://kotlinlang.org/docs/mpp-supported-platforms.html">on multiple platforms</a>. In August of 2020, JetBrains introduced <a href="https://blog.jetbrains.com/kotlin/2020/08/kotlin-multiplatform-mobile-goes-alpha/">Kotlin Multiplatform Mobile</a> (KMM), an SDK that facilitates the use of shared code across Android and iOS. The purpose of the technology is to extract business logic while the UI layer remains native, which is good for a better user experience and the look and feel of the apps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/771/1*6V512VrZsRK-4rzjmzlWXw.png" /></figure><h3>Why We Chose Kotlin Multiplatform</h3><p>We studied various cross-platform technologies. For example, React Native and Flutter allow to program and develop a feature for both platforms at the same time, but they narrow the choice of programming language and libraries. We chose Kotlin Multiplatform for three reasons.</p><h4>Ease of integration</h4><p>The common code written in Kotlin can be injected with minimal effort into a finished application. It then compiles into platform-familiar libraries. In the case of Android, it is a jar or aar library, while for iOS, it is a Universal Framework. The connection process and further work does not differ from interacting with any native library.</p><h4>The Kotlin language’s syntax is close to Swift</h4><p>The similarity of the language lowers the entry barrier for iOS developers. Both languages ​​share a similar ideology focused on development speed and usability. Anyone on the team can understand what is going on in the common code and adjust it if needed.</p><h4>Developer Resource Waste Minimization</h4><p>The business logic of our applications is the same. More than 70% of the code is not related to the platform it runs on. We request data from the server, transform it, cache it, and prepare it for display. Without code sharing, we would have to write the same code in Kotlin for Android and in Swift for iOS. There are differences only in the design side due to the difference in the UX on mobile platforms and the interaction with the system in terms of requests to various peripherals like cameras, geolocation, galleries, notifications, and so on.</p><h3>The Implementation Process</h3><p>We decided to act thoughtfully and methodically. We started by tackling simple problems, gradually increasing the complexity. We reflected on every stage, assessing the costs, the results and the consequences. Here is a description of the three main steps we have taken.</p><h4>Step 1. The first line in the shared code</h4><p>The first task is to make common API request strings to avoid differences in the requested data structures on the two platforms.</p><p>Data exchange with the server was implemented via GraphQL. The code request consisted of a multiline string made up of five lines, sometimes under a hundred. When sending such a volume of code, the backend will have to spend time parsing the structure. On the other hand, there is a need to control the requested data during the code review process and the validation of production requests. Therefore, we “train” the server for new requests before release. This allows hashes to be used instead of strings.</p><p>Previously, we did the “training” of the server manually and separately for each platform. This took up a lot of resources and increased the likelihood of errors. For example, it is easy to forget to “train” a request on one of the platforms and corrupt the application as a result.</p><p>We decided to move several requests into a shared code. A multiplatform shared module was developed for the purpose in the Android project. We moved the query strings into it and wrapped the object in singleton classes, and then called the methods of these classes in client applications. Fun fact — an iOS developer suggested using KMM.</p><p><em>The first line in the shared code</em></p><pre>package ru.profi.shared.queries.client.city  </pre><pre><em>/** <br>* </em>City search query by<em> [Params.term]<br>*/<br></em>object GeoSelectorWarpQuery : WarpQuery&lt;Params&gt; {</pre><pre>     override val hash: String? = &quot;\$GQLID{c9d4adbb7b9ef49fc044064b9a3e662b}&quot;</pre><pre>     override val dirtyQuery = listOf(&quot;\$term&quot;).let { (term) -&gt;<br>        &quot;&quot;&quot;<br>        query geoSelector($term: String) {<br>          suggestions: simpleGeoSelector(term: $term, first: 100) {<br>            edges {<br>              node {<br>                name<br>                geoCityId<br>                regionName<br>                hostname<br>                countryId<br>              }<br>            }<br>          }<br>        }<br>        &quot;&quot;&quot;<br>    }.trimIndent()<br> }</pre><p><em>Application in an Android project</em></p><pre>override fun getQuery() = GeoSelectorWarpQuery.getQuery()</pre><p><em>Application in an iOS project</em></p><pre>import KotlinComponents</pre><pre>struct GraphQLWarpRequests {<br>      static let GeoSelectorWarpQuery = GeoSelectorWarpQuery()<br>          ...<br>}</pre><pre>let model = GraphQLRequestModel(query: GraphQLWarpRequests.GeoSelectorWarpQuery.getQuery(), variables: variables)</pre><p>The query structures were relocated into a single repository. In the next release, the shared library was connected on both platforms and the system started operating smoothly. The app size on iOS was increased by only 0.8 MB. As a result, the transfer of requests into the shared code halved the number of approaches needed for the “training”.</p><p>The manual learning problem was solved with a utilitarian library consisting of several classes written in Kotlin. It finds untrained requests in the code, generates and then sends new hashes via a pull request to the backend repository. This allows us to avoid wasting time on training, as it is fully automated.</p><p>In this step, we built a framework for the shared code on Kotlin Multiplatform, allowing us to move on to more important tasks.</p><h4>Step 2. Creating a multiplatform SDK</h4><p>At one point, the company decided to create its own in-house analytics tool based on <a href="https://clickhouse.tech/">Clickhouse</a>. An API for applications was created for this purpose on the backend side. All my team had to do was send events. In order not to interfere with the work of the main functionality and avoid losing events if the user did not have a network, it was necessary to learn how to cache, group batches of events, and send them with a lower priority than requests for the main functionality.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5gfga095rBbEpdfClBh09Q.png" /></figure><p>We decided to write the module in the shared code. We selected the <a href="https://ktor.io/">Ktor network client</a> for this purpose, as it suited us perfectly.</p><p>When a network is lacking, the events must be saved until the next communication session. We chose <a href="https://cashapp.github.io/sqldelight/">SQLDelight</a> for the purpose — a multiplatform library for a native database.</p><p>For asynchronous operations, we used <a href="https://github.com/Kotlin/kotlinx.coroutines">kotlinx.coroutines</a> and <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a> for the serialization and deserialization processes.</p><p>To increase the reliability of the code, the functionality of the module was ensured with unit tests, since <a href="https://kotlinlang.org/docs/mpp-run-tests.html">they can be run on different platforms with ease</a>.</p><p>There were no problems with integrating the application on Android, but there were crashes at the start on iOS. The stack trace did not get us very close to our goal on the Xcode console and Firebase Crashlytics logs. But the failing element inside the shared code was evident to us.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/748/1*y_Irjlsuiluez66ScdK4EQ.png" /></figure><p>To get a stack trace, we included the <a href="https://github.com/touchlab/CrashKiOS">CrashKiOS library</a> from the Touchlab studio. When creating the coroutines, we added the <a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/">CoroutineExceptionHandler</a>, which identifies exceptions during their execution.</p><p>It turned out that the event was dispatched after the coroutine’s scope was canceled. And this was the reason for the failures. It turned out that we had incorrectly canceled CoroutineScope in the application lifecycle.</p><p>Kotlin Multiplatform made it possible to combine the responsibility for sending and storing analytical events into a single module. As a result, we built a full-fledged SDK in a shared code.</p><h4>Step 3. Transfer of the business logic from the Android application to the multiplatform</h4><p>I am certain that many projects have code that they want to bypass, since it is difficult to read, regularly causes subtle problems with the product, and was written so long ago that its authors are no longer with the company.</p><p>The iOS app had this code in the chat business logic module. This was our pain point, as it became more expensive to add new functionality, since the code was written in Objective-C with an outdated and complex architecture. We felt that the developers were reluctant to take on chat tasks.</p><p>In an Android application, the chat business logic has recently been rewritten to Kotlin. Therefore, we decided to try to move the existing module into the shared code and adapt it to iOS.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-NkduOtjar62_M5ytWQrNQ.png" /></figure><p>The developers from <a href="https://icerockdev.com/">IceRock</a> helped us a lot. They have long embarked on the path of multiplatform operations, promoting KMM and developing <a href="https://twitter.com/KotlinMPP">the community</a>. And together, we drew up a plan.</p><p><strong>Configure Kotlin Multiplatform support in the gradle module.</strong></p><p>Create a module, connect plugins, configure the sourceSets and dependencies.</p><p><strong>Move platform independent classes to commonMain.</strong></p><p>Move all the elements of JVM and Android independent to commonMain. This is a repository for common code that has no platform dependencies.</p><p><strong>Replace JVM / Android libraries with their multiplatform counterparts.</strong></p><p>Move from org.json to <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a> and from JodaTime to <a href="https://github.com/korlibs/klock">klock</a>. Some parts had to be moved into the platform-dependent code in the form of expect/actual.</p><p><strong>Move the JVM-dependent code that requires changes to commonMain.</strong></p><p>For example, replace JVM IOException with kotlin.Exception and ConcurrentHashMap with <a href="https://github.com/touchlab/Stately">Stately</a>.</p><p><strong>Move the Android dependent code that requires changes to commonMain.</strong></p><p>The only Android SDK dependency was the Service component, which works with WebSocket, since there is no stable multiplatform analogue on Kotlin yet.</p><p>We decided to leave the native implementations in the application and connect them through the SocketService interface.</p><p><em>The SocketService interface</em></p><pre>interface SocketService {</pre><pre><em>      /**<br>      * </em>Connect on a socket to<em> [chatUrl]. </em>All events from the socket must be sent to<em> [callback]<br>      */<br></em>     fun connect(chatUrl: String, callback: (SocketEvent) -&gt; Unit)</pre><pre><em>      /**<br>      * </em>Disconnect from the current socket connection<em><br>      */<br></em>     fun disconnect()</pre><pre><em>      /**<br>      * </em>Send message [msg] on current socket connection<em><br>      */<br></em>     fun send(msg: String)<br> }</pre><p><strong>Making the API module usable for both platforms.</strong></p><p>Since it is impossible to detect runtime exceptions from Kotlin on iOS, we decided to handle them inside the SDK and add callback onError to the interface methods. We had to slightly redesign the interface for interacting with client applications.</p><p>When transferring the code to the multiplatform, we formulated an algorithm for migrating the modules with the business logic into a shared code. We use it to extract other modules.</p><p>IceRock.dev’s plan helped us a lot with moving faster and with greater confidence. We will surely continue to contact them as needed and share our development experience.</p><h3><strong>What We Can Conclude</strong></h3><p><strong>Kotlin Multiplatform helped create a single source of business logic in Profi client applications.</strong> The UI and UX were left native for the user. With the right design of the interface for interacting with shared code, the change and extension of business logic takes place from a single source, and client applications just need to support this approach.</p><p><strong>We reduced waste of resources.</strong> When migrating modules to Kotlin Multiplatform, we noticed how much development time was saved, as the chat module on iOS did not have to be refactored. Instead, we moved the solution from the Android project into the shared code and adapted it for iOS. It costs less than writing chats from scratch.</p><p><strong>The developers quickly got used to the process.</strong> The only new elements for Android developers were the multiplatform libraries and module build-script customization. The rest was familiar territory. It was easy for iOS developers to understand the syntax of the language, but they had to dig into the assembly using Gradle. And now, each of them has already solved at least one problem in the shared code.</p><p><strong>The main disadvantage of the technology is the build time for iOS.</strong> For example, when we were looking for the reason for the application’s crash, we often had to rebuild the shared code for iOS. This is barely noticable when publishing shared modules. With the release of new versions of Kotlin, the build speed increases, we hope that future development cycles will be more convenient.</p><p><strong>We did encounter some problems as a result of our ignorance.</strong> When we started on the project, there was very little information on the implementation of KMM, so we had to solve all of the issues ourselves. The Kotlin Multiplatform community is growing rapidly with more articles and reports appearing at conferences and meetups. There are channels in <a href="https://surveys.jetbrains.com/s3/kotlin-slack-sign-up">Slack</a>, <a href="https://github.com/AAkira/Kotlin-Multiplatform-Libraries">libraries for Kotlin Multiplatform</a>, and so on, plenty of information sources available.</p><h3>It Was Worth It In The End</h3><p>The first steps were difficult, because the technology was new. At first, it seemed easier to use native solutions on libraries known to the team than to understand new ones. But we understood that things would unravel faster later on. And we were right. We can say that the speed of development using shared code and native projects is fairly equal.</p><p>We already have 10 common modules of varying complexity, and we are continuing to migrate the business logic into a shared code. I am sure that Kotlin Multiplatform Mobile is ready to conquer the world of mobile application development.</p><blockquote>Read more about KMM <a href="https://icerockdev.com/blog/kotlin-multiplatform">in our blog</a>.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c497059f2f48" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/how-we-integrated-kotlin-multiplatform-into-profi-c497059f2f48">How We integrated Kotlin Multiplatform Into Profi</a> was originally published in <a href="https://medium.com/icerock">IceRock Development</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>