<?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[Stories by IceRock Development on Medium]]></title>
        <description><![CDATA[Stories by IceRock Development on Medium]]></description>
        <link>https://medium.com/@icerock?source=rss-2f461fccc401------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*GI4a7QPl8mwKeWrSGjAgJQ.png</url>
            <title>Stories by IceRock Development on Medium</title>
            <link>https://medium.com/@icerock?source=rss-2f461fccc401------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 07 Apr 2026 02:14:40 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@icerock/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <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-2f461fccc401------2</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, 07 Apr 2023 12:08:45 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[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-2f461fccc401------2</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>Thu, 16 Feb 2023 08:46:23 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[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-2f461fccc401------2</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>Thu, 09 Feb 2023 10:58:46 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[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-2f461fccc401------2</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>Wed, 25 Jan 2023 06:55:43 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[KMP vs Flutter vs React Native]]></title>
            <link>https://medium.com/icerock/kmp-vs-flutter-vs-react-native-cb7d1958fab3?source=rss-2f461fccc401------2</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>Mon, 25 Oct 2021 14:51:05 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[How We integrated Kotlin Multiplatform Into Profi]]></title>
            <link>https://medium.com/icerock/how-we-integrated-kotlin-multiplatform-into-profi-c497059f2f48?source=rss-2f461fccc401------2</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 06:23:00 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>
        <item>
            <title><![CDATA[Creating a simple Kotlin Multiplatform project based on moko-template — part 2]]></title>
            <link>https://medium.com/icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-part-2-4444ca710709?source=rss-2f461fccc401------2</link>
            <guid isPermaLink="false">https://medium.com/p/4444ca710709</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Sun, 17 Nov 2019 12:05:17 GMT</pubDate>
            <atom:updated>2023-05-10T11:59:23.127Z</atom:updated>
            <content:encoded><![CDATA[<h3>Creating a simple Kotlin Multiplatform project based on moko-template — part 2</h3><h3>1. Intro</h3><p>This manual is the second part in GiphyApp series, before you start we would recommend to do <a href="https://medium.com/@icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-2dd87d020bbd">GiphyApp</a> #1.</p><p>The result of this lession is available on <a href="https://github.com/Alex009/giphy-mobile">github</a>.</p><h3>2. Implement common logic of Gif list in shared library</h3><p>App should get list of Gifs from GIPHY service. There is an example with getting list of news from newsapi in the project template (using <a href="https://github.com/icerockdev/moko-network">moko-network</a> with generating network entites and API classes from OpenAPI specification).</p><p>We can get OpenAPI spec of GIPHY from <a href="https://apis.guru/browse-apis/">apis.guru</a> and can replace getting news by getting Gif.</p><p>Feature List is already in the project template and you have not to implement any additional logic. You can see <a href="https://github.com/icerockdev/moko-template#list-module-scheme">scheme of module</a> and look into mpp-library:feature:list for detail information about it.</p><h3>Replace OpenAPI spec</h3><p>Replace file mpp-library/domain/src/openapi.yml by the content from <a href="https://codelabs.kmp.icerock.dev/codelabs/giphy-app-2/assets/giphy-openapi.yml">OpenAPI spec of GIPHY service</a>. After it please do Gradle Sync and as the result you will see some errors in the newsapi code. Let&#39;s update code by new API.</p><p>You can find generated files here mpp-library/domain/build/generate-resources/main/src/main/kotlin</p><h3>Replace news by gifs in domain module</h3><p>You have to update the following classes after replacing OpenAPI spec in domain module:</p><ul><li>News should be replaced by Gif;</li><li>NewsRepository – should be replaced by GifRepository;</li><li>DomainFactory – add gifRepository and set necessary dependencies.</li></ul><h3>News -&gt; Gif</h3><p>Let’s modify News class to the following one:</p><pre>@Parcelize<br>data class Gif(<br>    val id: Int,<br>    val previewUrl: String,<br>    val sourceUrl: String<br>) : Parcelable</pre><p>This domain entity contains gif’s id and two URL (full and preview variant). id is used for correct identifying element in a list and in UI animations.</p><p>Let’s transform network entity dev.icerock.moko.network.generated.models.Gif to domain entity. To do this add one more construct method:</p><pre>@Parcelize<br>data class Gif(<br>    ...<br>) : Parcelable {</pre><pre>    internal constructor(entity: dev.icerock.moko.network.generated.models.Gif) : this(<br>        id = entity.url.hashCode(),<br>        previewUrl = requireNotNull(entity.images?.downsizedMedium?.url) { &quot;api can&#39;t respond without preview image&quot; },<br>        gifUrl = requireNotNull(entity.images?.original?.url) { &quot;api can&#39;t respond without original image&quot; }<br>    )<br>}</pre><p>Above there is a field mapping from network entity to domain entity — it will reduce the number of edits if API has been changed. The application doesn’t depend on API implementation.</p><h3>NewsRepository -&gt; GifRepository</h3><p>Let’s change NewsRepository to GifRepository with the following content:</p><pre>class GifRepository internal constructor(<br>    private val gifsApi: GifsApi<br>) {<br>    suspend fun getGifList(query: String): List&lt;Gif&gt; {<br>        return gifsApi.searchGifs(<br>            q = query,<br>            limit = null,<br>            offset = null,<br>            rating = null,<br>            lang = null<br>        ).data?.map { Gif(entity = it) }.orEmpty()<br>    }<br>}</pre><p>In this class you have to get GifsApi object (generated by moko-network) and call a method API searchGifs, where we use just query string, but other arguments are by default.</p><p>Network entities we have to modify in domain entities, what can be public (network entites generated with internalmodifier only).</p><h3>DomainFactory</h3><p>In DomainFactory we have to replace creation newsApi and newsRepository by the following code:</p><pre>private val gifsApi: GifsApi by lazy {<br>    GifsApi(<br>        basePath = baseUrl,<br>        httpClient = httpClient,<br>        json = json<br>    )<br>}</pre><pre>val gifRepository: GifRepository by lazy {<br>    GifRepository(<br>        gifsApi = gifsApi<br>    )<br>}</pre><p>GifsApi - it&#39;s a generated class, for creation you need a several parameters:</p><ul><li>baseUrl – server url, it will come from factory of native layer. It needed for set up different envoiroment configuration.</li><li>httpClient - http client object for work with server (from <a href="https://github.com/ktorio/ktor/">ktor-client</a>)</li><li>json - JSON serialization object (from <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>)</li></ul><p>GifRepository is available outside of module, you can create it using gifsApi object only.</p><p>There is a lazy initialization – API and repository are Singleton objects (objects are alive while the factory is alive and the factory is created SharedFactory exists during life cycle of an application).</p><p>Also we need to send Api Key for work with GIPHY API. To do this we can use TokenFeature for ktor. It was already connected, we just have to configure it:</p><pre>install(TokenFeature) {<br>    tokenHeaderName = &quot;api_key&quot;<br>    tokenProvider = object : TokenFeature.TokenProvider {<br>        override fun getToken(): String? = &quot;o5tAxORWRXRxxgIvRthxWnsjEbA3vkjV&quot;<br>    }<br>}</pre><p>Every query comes throught httpClient will be append by header api_key: o5tAxORWRXRxxgIvRthxWnsjEbA3vkjV in this case (this is a sample app key, you can create a your one in <a href="https://developers.giphy.com/dashboard/">GIPHY admin area</a> if you are exceed the limit).</p><h3>Update connection between domain and feature:list from SharedFactory</h3><p>In SharedFactory we have to change interface of units list factory, NewsUnitsFactory, and replace singleton newsFactory by gifsFactory with Gif configuration.</p><h3>NewsUnitsFactory -&gt; GifsUnitsFactory</h3><p>Interface of units list factory should be replaced by:</p><pre>interface GifsUnitsFactory {<br>    fun createGifTile(<br>        id: Long,<br>        gifUrl: String<br>    ): UnitItem<br>}</pre><p>So, there will be id (for proper diff list calculation for UI animation ) and gifUrl (this is url for animation output) from shared code.</p><h3>newsFactory -&gt; gifsFactory</h3><p>List Factory should be replaced by the following code:</p><pre>val gifsFactory: ListFactory&lt;Gif&gt; = ListFactory(<br>    listSource = object : ListSource&lt;Gif&gt; {<br>        override suspend fun getList(): List&lt;Gif&gt; {<br>            return domainFactory.gifRepository.getGifList(&quot;test&quot;)<br>        }<br>    },<br>    strings = object : ListViewModel.Strings {<br>        override val unknownError: StringResource = MR.strings.unknown_error<br>    },<br>    unitsFactory = object : ListViewModel.UnitsFactory&lt;Gif&gt; {<br>        override fun createTile(data: Gif): UnitItem {<br>            return gifsUnitsFactory.createGifTile(<br>                id = data.id.toLong(),<br>                gifUrl = data.previewUrl<br>            )<br>        }<br>    }<br>)</pre><p>In code above there is a data source listSource and we call gifRepository from domain module there. Temporary query is set up as test value, but we will change it in next lessons.<br>Also there is a parameter strings, localization strings, will be implemented in feature:list module (this module requires only one string &quot;unknown error&quot;).<br>The last required parameter is unitsFactory, but the module works with 1 method factory, createTile(data: Gif), and for native platforms it will be better to use a specific list factory (so every UI-related field was defined from common code). That&#39;s why we use gifsUnitsFactory.createGifTile.</p><p>The last thing to do — replace SharedLibrary constructor by the following code:</p><pre>class SharedFactory(<br>    settings: Settings,<br>    antilog: Antilog,<br>    baseUrl: String,<br>    gifsUnitsFactory: GifsUnitsFactory<br>)</pre><p>So native platforms will return GifsUnitsFactory object.</p><h3>3. Implement Gif list on Android</h3><h3>Set server URL</h3><p>There is a working server URL will be passed from application layer to the common code library so we avoid rebuilding when server url had changed.</p><p>In our current configuration there is only one environment and only one server url. It set up in android-app/build.gradle.kts, let&#39;s replace it:</p><pre>android {<br>    ...<br>    defaultConfig {<br>        ...</pre><pre>        val url = &quot;https://api.giphy.com/v1/&quot;<br>        buildConfigField(&quot;String&quot;, &quot;BASE_URL&quot;, &quot;\&quot;$url\&quot;&quot;)<br>    }<br>}</pre><h3>Dependencies Injection</h3><p>We have to use <a href="https://github.com/bumptech/glide">glide</a> library for gif rendering and we use <a href="https://developer.android.com/training/constraint-layout">constraintLayout</a> library for setting aspect ratio 2:1 of list’s unit.</p><p>constraintLayout is already declared in project dependencies and we just need to include it on android-app, let&#39;s add it in android-app/build.gradle.kts:</p><pre>dependencies {<br>    ...<br>    implementation(Deps.Libs.Android.constraintLayout.name)<br>}</pre><p>A glide has to be appended in dependencies injection script in buildSrc/src/main/kotlin/Versions.kt:</p><pre>object Versions {<br>    ...<br>    object Libs {<br>        ...<br>        object Android {<br>            ...<br>            const val glide = &quot;4.10.0&quot;<br>        }<br>    }<br>}</pre><p>And in buildSrc/src/main/kotlin/Deps.kt:</p><pre>object Deps {<br>    ...<br>    object Libs {<br>        ...<br>        object Android {<br>            ...<br>            val glide = AndroidLibrary(<br>                name = &quot;com.github.bumptech.glide:glide:${Versions.Libs.Android.glide}&quot;<br>            )<br>        }</pre><p>After this we can add in android-app/build.gradle.kts the following code:</p><pre>dependencies {<br>    ...<br>    implementation(Deps.Libs.Android.glide.name)<br>}</pre><h3>SharedFactory Initialization</h3><p>To create SharedFactory you have to replace newsUnitsFactory by gifsUnitsFactory. To create this dependency let&#39;s modify NewsUnitsFactory class to the following:</p><pre>class GifListUnitsFactory : SharedFactory.GifsUnitsFactory {<br>    override fun createGifTile(id: Long, gifUrl: String): UnitItem {<br>        TODO()<br>    }<br>}</pre><p>And we should use it in SharedFactory:</p><pre>AppComponent.factory = SharedFactory(<br>    baseUrl = BuildConfig.BASE_URL,<br>    settings = AndroidSettings(getSharedPreferences(&quot;app&quot;, Context.MODE_PRIVATE)),<br>    antilog = DebugAntilog(),<br>    gifsUnitsFactory = GifListUnitsFactory()<br>)</pre><h3>GifListUnitsFactory Implementation</h3><p>SharedFactory.GifsUnitsFactory interface requires to create UnitItem from id and gifUrl variables. UnitItem is a part of <a href="https://github.com/icerockdev/moko-units">moko-units</a> and you can generate implementation from a DataBinding layout.</p><p>Let’s create android-app/src/main/res/layout/tile_gif.xml with the following content:</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br>    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;<br>    xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;</pre><pre>    &lt;data&gt;</pre><pre>        &lt;variable<br>            name=&quot;gifUrl&quot;<br>            type=&quot;String&quot; /&gt;<br>    &lt;/data&gt;</pre><pre>    &lt;androidx.constraintlayout.widget.ConstraintLayout<br>        android:layout_width=&quot;match_parent&quot;<br>        android:layout_height=&quot;wrap_content&quot;<br>        android:padding=&quot;8dp&quot;&gt;</pre><pre>        &lt;ImageView<br>            android:layout_width=&quot;match_parent&quot;<br>            android:layout_height=&quot;0dp&quot;<br>            app:gifUrl=&quot;@{gifUrl}&quot;<br>            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;<br>            app:layout_constraintDimensionRatio=&quot;2:1&quot;<br>            app:layout_constraintLeft_toLeftOf=&quot;parent&quot;<br>            app:layout_constraintRight_toRightOf=&quot;parent&quot;<br>            app:layout_constraintTop_toTopOf=&quot;parent&quot;<br>            tools:ignore=&quot;ContentDescription&quot; /&gt;<br>    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;<br>&lt;/layout&gt;</pre><p>And run Gradle Sync after this – TileGif class will be generated automatically and we will use it in GifListUnitsFactory class.</p><pre>class GifListUnitsFactory : SharedFactory.GifsUnitsFactory {<br>    override fun createGifTile(id: Long, gifUrl: String): UnitItem {<br>        return TileGif().apply {<br>            itemId = id<br>            this.gifUrl = gifUrl<br>        }<br>    }<br>}</pre><p>In the layout we use non-standart Binding Adapter — app:gifUrl. We should implement it. To do this let&#39;s create android-app/src/main/java/org/example/app/BindingAdapters.kt file with the following code:</p><pre>package org.example.app</pre><pre>import android.widget.ImageView<br>import androidx.databinding.BindingAdapter<br>import androidx.swiperefreshlayout.widget.CircularProgressDrawable<br>import com.bumptech.glide.Glide</pre><pre>@BindingAdapter(&quot;gifUrl&quot;)<br>fun ImageView.bindGif(gifUrl: String?) {<br>    if (gifUrl == null) {<br>        this.setImageDrawable(null)<br>        return<br>    }</pre><pre>    val circularProgressDrawable = CircularProgressDrawable(context).apply {<br>        strokeWidth = 5f<br>        centerRadius = 30f<br>        start()<br>    }</pre><pre>    Glide.with(this)<br>        .load(gifUrl)<br>        .placeholder(circularProgressDrawable)<br>        .error(android.R.drawable.stat_notify_error)<br>        .into(this)<br>}</pre><p>This allows us to set gifUrl for ImageView from layout. Moreover on loading there will be progress bar and on error it will be error icon.</p><h3>Create a Gif list screen</h3><p>All that’s left to do a screen showing data from our common code.<br>Create android-app/src/main/res/layout/activity_gif_list.xml with the content:</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br>    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;&gt;</pre><pre>    &lt;data&gt;<br>        &lt;import type=&quot;org.example.library.domain.entity.Gif&quot;/&gt;<br>        &lt;import type=&quot;org.example.library.feature.list.presentation.ListViewModel&quot; /&gt;</pre><pre>        &lt;variable<br>            name=&quot;viewModel&quot;<br>            type=&quot;ListViewModel&amp;lt;Gif&amp;gt;&quot; /&gt;<br>    &lt;/data&gt;</pre><pre>    &lt;FrameLayout<br>        android:layout_width=&quot;match_parent&quot;<br>        android:layout_height=&quot;match_parent&quot;&gt;</pre><pre>        &lt;androidx.swiperefreshlayout.widget.SwipeRefreshLayout<br>            android:id=&quot;@+id/refresh_layout&quot;<br>            android:layout_width=&quot;match_parent&quot;<br>            android:layout_height=&quot;match_parent&quot;<br>            app:visibleOrGone=&quot;@{viewModel.state.ld.isSuccess}&quot;&gt;</pre><pre>            &lt;androidx.recyclerview.widget.RecyclerView<br>                android:layout_width=&quot;match_parent&quot;<br>                android:layout_height=&quot;match_parent&quot;<br>                app:adapter=&quot;@{`dev.icerock.moko.units.adapter.UnitsRecyclerViewAdapter`}&quot;<br>                app:bindValue=&quot;@{viewModel.state.ld.dataValue}&quot;<br>                app:layoutManager=&quot;androidx.recyclerview.widget.LinearLayoutManager&quot; /&gt;</pre><pre>        &lt;/androidx.swiperefreshlayout.widget.SwipeRefreshLayout&gt;</pre><pre>        &lt;ProgressBar<br>            android:layout_width=&quot;wrap_content&quot;<br>            android:layout_height=&quot;wrap_content&quot;<br>            android:layout_gravity=&quot;center&quot;<br>            app:visibleOrGone=&quot;@{viewModel.state.ld.isLoading}&quot; /&gt;</pre><pre>        &lt;TextView<br>            android:layout_width=&quot;match_parent&quot;<br>            android:layout_height=&quot;wrap_content&quot;<br>            android:layout_gravity=&quot;center&quot;<br>            android:gravity=&quot;center&quot;<br>            android:text=&quot;@string/no_data&quot;<br>            app:visibleOrGone=&quot;@{viewModel.state.ld.isEmpty}&quot; /&gt;</pre><pre>        &lt;LinearLayout<br>            android:layout_width=&quot;match_parent&quot;<br>            android:layout_height=&quot;wrap_content&quot;<br>            android:layout_gravity=&quot;center&quot;<br>            android:padding=&quot;16dp&quot;<br>            android:orientation=&quot;vertical&quot;<br>            app:visibleOrGone=&quot;@{viewModel.state.ld.isError}&quot;&gt;</pre><pre>            &lt;TextView<br>                android:layout_width=&quot;match_parent&quot;<br>                android:layout_height=&quot;wrap_content&quot;<br>                android:gravity=&quot;center&quot;<br>                android:text=&quot;@{viewModel.state.ld.errorValue}&quot; /&gt;</pre><pre>            &lt;Button<br>                android:layout_width=&quot;wrap_content&quot;<br>                android:layout_height=&quot;wrap_content&quot;<br>                android:layout_gravity=&quot;center_horizontal&quot;<br>                android:onClick=&quot;@{() -&gt; viewModel.onRetryPressed()}&quot;<br>                android:text=&quot;@string/retry_btn&quot; /&gt;<br>        &lt;/LinearLayout&gt;<br>    &lt;/FrameLayout&gt;<br>&lt;/layout&gt;</pre><p>Layout uses Data Binding and show 1 of the 4 states got from ListViewModel. There is SwipeRefreshLayoutwith RecyclerView inside in data state, and RecyclerView uses LinearLayoutManager and UnitsRecyclerViewAdapter for rendering UnitItem objectes that got from UnitsFactory.</p><p>Let’s create android-app/src/main/java/org/example/app/view/GifListActivity.kt with the content:</p><pre>class GifListActivity : MvvmActivity&lt;ActivityGifListBinding, ListViewModel&lt;Gif&gt;&gt;() {<br>    override val layoutId: Int = R.layout.activity_gif_list<br>    override val viewModelClass = ListViewModel::class.java as Class&lt;ListViewModel&lt;Gif&gt;&gt;<br>    override val viewModelVariableId: Int = BR.viewModel</pre><pre>    override fun viewModelFactory(): ViewModelProvider.Factory = createViewModelFactory {<br>        AppComponent.factory.gifsFactory.createListViewModel()<br>    }</pre><pre>    override fun onCreate(savedInstanceState: Bundle?) {<br>        super.onCreate(savedInstanceState)</pre><pre>        with(binding.refreshLayout) {<br>            setOnRefreshListener {<br>                viewModel.onRefresh { isRefreshing = false }<br>            }<br>        }<br>    }<br>}</pre><p>We’ve got ListViewModel&lt;Gif&gt; from gifsFactory factory and it will be inserted in viewModel field from activity_gif_list layout.</p><p>Also we define setOnRefreshListener in code for proper execution SwipeRefreshLayout and call viewModel.onRefresh that report in lambda when update will be finished and we can turn off the updating animation.</p><h3>Replace a startup screen</h3><p>Let’s set up GifListActivity as a launch screen. To do it let&#39;s add GifListActivity in android-app/src/main/AndroidManifest.xml file and remove others (we don&#39;t need it any more).</p><pre>&lt;application ...&gt;</pre><pre>    &lt;activity android:name=&quot;.view.GifListActivity&quot; &gt;<br>        &lt;intent-filter&gt;<br>            &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;<br>            &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;<br>        &lt;/intent-filter&gt;<br>    &lt;/activity&gt;<br>&lt;/application&gt;</pre><h3>Remove unnecessary classes</h3><p>Now we can delete all unnnecessary files from project template:</p><ul><li>android-app/src/main/java/org/example/app/view/ConfigActivity.kt</li><li>android-app/src/main/java/org/example/app/view/NewsActivity.kt</li><li>android-app/src/main/res/layout/activity_news.xml</li><li>android-app/src/main/res/layout/tile_news.xml</li></ul><h3>Run</h3><p>You can run the application on Android and see list of Gifs.</p><h3>4. Implement Gif list on iOS</h3><h3>Set server URL</h3><p>As well as on Android, a working server URL will be passed from application layer to the common code library so we avoid rebuilding common library when server url had changed.<br>This setting can be set in ios-app/src/AppDelegate.swift file:</p><pre>AppComponent.factory = SharedFactory(<br>    ...<br>    baseUrl: &quot;https://api.giphy.com/v1/&quot;,<br>    ...<br>)</pre><h3>Dependencies Injections</h3><p>We have to use <a href="https://github.com/kirualex/SwiftyGif">SwiftyGif</a> for showing gif files. To include it we have to inject ios-app/Podfile dependency:</p><pre>target &#39;ios-app&#39; do<br>  ...<br>  pod &#39;SwiftyGif&#39;, &#39;5.1.1&#39;<br>end</pre><p>and after this we can run a pod install command in ios-app directory.</p><h3>SharedFactory Initialization</h3><p>We have to use gifsUnitsFactory instead of newsUnitsFactory to create SharedFactory. To do this let&#39;s modify NewsUnitsFactory class in following code:</p><pre>class GifsListUnitsFactory: SharedFactoryGifsUnitsFactory {<br>    func createGifTile(id: Int64, gifUrl: String) -&gt; UnitItem {<br>        <em>// TODO</em><br>    }<br>}</pre><p>And will pass it in SharedFactory:</p><pre>AppComponent.factory = SharedFactory(<br>    settings: AppleSettings(delegate: UserDefaults.standard),<br>    antilog: DebugAntilog(defaultTag: &quot;MPP&quot;),<br>    baseUrl: &quot;https://api.giphy.com/v1/&quot;,<br>    gifsUnitsFactory: GifsListUnitsFactory()<br>)</pre><h3>GifListUnitsFactory implementation</h3><p>SharedFactory.GifsUnitsFactory interface requires to create UnitItem from id and gifUrl variables. UnitItem is a part of <a href="https://github.com/icerockdev/moko-units">moko-units</a> and implementation requires to create xib with cell interface and specific cell class.</p><p>Create ios-app/src/units/GifTableViewCell.swift with the content:</p><pre>import MultiPlatformLibraryUnits<br>import SwiftyGif</pre><pre>class GifTableViewCell: UITableViewCell, Fillable {<br>    typealias DataType = CellModel<br>    <br>    struct CellModel {<br>        let id: Int64<br>        let gifUrl: String<br>    }<br>    <br>    @IBOutlet private var gifImageView: UIImageView!<br>    <br>    private var gifDownloadTask: URLSessionDataTask?<br>    <br>    override func prepareForReuse() {<br>        super.prepareForReuse()<br>        <br>        gifDownloadTask?.cancel()<br>        gifImageView.clear()<br>    }<br>    <br>    func fill(_ data: GifTableViewCell.CellModel) {<br>        gifDownloadTask = gifImageView.setGifFromURL(URL(string: data.gifUrl)!)<br>    }<br>    <br>    func update(_ data: GifTableViewCell.CellModel) {<br>        <br>    }<br>}</pre><pre>extension GifTableViewCell: Reusable {<br>    static func reusableIdentifier() -&gt; String {<br>        return &quot;GifTableViewCell&quot;<br>    }<br>    <br>    static func xibName() -&gt; String {<br>        return &quot;GifTableViewCell&quot;<br>    }<br>    <br>    static func bundle() -&gt; Bundle {<br>        return Bundle.main<br>    }<br>}</pre><p>Then create ios-app/src/units/GifTableViewCell.xib with a cell layout.</p><p>The result looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hRPMdrME_fUq8tmv.png" /></figure><p>We have to set GifTableViewCell class in UITableViewCell cell:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/516/0*1_iKN3dPE55VNAwI.png" /></figure><p>And set an identifier for reuse:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*VWwNRGNVzSNPV5Py.png" /></figure><p>Now we can implement UnitItem creation in GifListUnitsFactory:</p><pre>class GifsListUnitsFactory: SharedFactoryGifsUnitsFactory {<br>    func createGifTile(id: Int64, gifUrl: String) -&gt; UnitItem {<br>        return UITableViewCellUnit&lt;GifTableViewCell&gt;(<br>            data: GifTableViewCell.CellModel(<br>                id: id,<br>                gifUrl: gifUrl<br>            ),<br>            configurator: nil<br>        )<br>    }<br>}</pre><h3>Create a Gif list screen</h3><p>All that’s left to do a screen showing data from our common code.</p><p>Create ios-app/src/view/GifListViewController.swift with the content:</p><pre>import MultiPlatformLibraryMvvm<br>import MultiPlatformLibraryUnits</pre><pre>class GifListViewController: UIViewController {<br>    @IBOutlet private var tableView: UITableView!<br>    @IBOutlet private var activityIndicator: UIActivityIndicatorView!<br>    @IBOutlet private var emptyView: UIView!<br>    @IBOutlet private var errorView: UIView!<br>    @IBOutlet private var errorLabel: UILabel!<br>    <br>    private var viewModel: ListViewModel&lt;Gif&gt;!<br>    private var dataSource: FlatUnitTableViewDataSource!<br>    private var refreshControl: UIRefreshControl!<br>    <br>    override func viewDidLoad() {<br>        super.viewDidLoad()<br>        <br>        viewModel = AppComponent.factory.gifsFactory.createListViewModel()</pre><pre>        <em>// binding methods from https://github.com/icerockdev/moko-mvvm</em><br>        activityIndicator.bindVisibility(liveData: viewModel.state.isLoadingState())<br>        tableView.bindVisibility(liveData: viewModel.state.isSuccessState())<br>        emptyView.bindVisibility(liveData: viewModel.state.isEmptyState())<br>        errorView.bindVisibility(liveData: viewModel.state.isErrorState())</pre><pre>        <em>// in/out generics of Kotlin removed in swift, so we should map to valid class</em><br>        let errorText: LiveData&lt;StringDesc&gt; = viewModel.state.error().map { $0 as? StringDesc } as! LiveData&lt;StringDesc&gt;<br>        errorLabel.bindText(liveData: errorText)</pre><pre>        <em>// datasource from https://github.com/icerockdev/moko-units</em><br>        dataSource = FlatUnitTableViewDataSource()<br>        dataSource.setup(for: tableView)</pre><pre>        <em>// manual bind to livedata, see https://github.com/icerockdev/moko-mvvm</em><br>        viewModel.state.data().addObserver { [weak self] itemsObject in<br>            guard let items = itemsObject as? [UITableViewCellUnitProtocol] else { return }<br>            <br>            self?.dataSource.units = items<br>            self?.tableView.reloadData()<br>        }<br>        <br>        refreshControl = UIRefreshControl()<br>        tableView.refreshControl = refreshControl<br>        refreshControl.addTarget(self, action: <em>#selector(onRefresh), for: .valueChanged)</em><br>    }<br>    <br>    @IBAction func onRetryPressed() {<br>        viewModel.onRetryPressed()<br>    }<br>    <br>    @objc func onRefresh() {<br>        viewModel.onRefresh { [weak self] in<br>            self?.refreshControl.endRefreshing()<br>        }<br>    }<br>}</pre><p>And let’s bind NewsViewController to GifListViewController in MainStoryboard:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*m3q472fapWI66kgz.png" /></figure><h3>Replace a startup screen</h3><p>To launch the application from gif screen we have to link rootViewController with GifListViewController in Navigation Controller:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*K8dEYMNfK9yXI4po.png" /></figure><h3>Remove unnecessary files</h3><p>Now we can delete all unnnecessary files from project:</p><ul><li>ios-app/src/units/NewsTableViewCell.swift</li><li>ios-app/src/units/NewsTableViewCell.xib</li><li>ios-app/src/view/ConfigViewController.swift</li><li>ios-app/src/view/NewsViewController.swift</li></ul><h3>Run</h3><p>Now you can run the application on iOS and see a list of Gif.</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=4444ca710709" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-part-2-4444ca710709">Creating a simple Kotlin Multiplatform project based on moko-template — part 2</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[Creating a simple Kotlin Multiplatform project based on moko-template]]></title>
            <link>https://medium.com/icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-2dd87d020bbd?source=rss-2f461fccc401------2</link>
            <guid isPermaLink="false">https://medium.com/p/2dd87d020bbd</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[mobile-development]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 08 Nov 2019 19:52:31 GMT</pubDate>
            <atom:updated>2023-05-10T12:01:41.718Z</atom:updated>
            <content:encoded><![CDATA[<h3>1. Intro</h3><p>In this lesson we will cover developing small application for iOS and Android using <a href="https://kotlinlang.org/docs/reference/multiplatform.html">Kotlin Multiplatform</a> based on <a href="https://github.com/icerockdev/moko-template">moko-template</a>.</p><h3>Tools</h3><p>We will need:</p><ul><li>Android Studio 3.4.0+ (do not use 3.5.1 version, cause there is a <a href="https://youtrack.jetbrains.com/issue/KT-34143">bug is breaking MPP project</a>);</li><li>Xcode 10.3+;</li><li>Xcode Command Line Tools (xcode-select --install);</li><li><a href="https://cocoapods.org/">CocoaPods</a> (sudo gem install cocoapods);</li><li><a href="https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">JDK</a> — требуется для запуска gradle из Xcode build phase.</li></ul><h3>The Result</h3><p>As the result of GiphyApp lessons you will get an application to view gif files using <a href="https://developers.giphy.com/docs/api">GIPHY API</a>.<br>UI of this application will be completely native, player of gif files will make using native libraries <a href="https://github.com/bumptech/glide">glide</a> for Android and <a href="https://github.com/kirualex/SwiftyGif">SwiftyGif</a> for iOS.</p><h3>2. Create the project based on moko-template</h3><p>For creation we will use project template from <a href="https://github.com/icerockdev/moko-template">moko-template</a>.</p><p>The project template already has preconfigured builds of iOS and Andoroid application with shared library and you will save the time to integrate shared library to iOS project on iOS platform, to configure Kotlin Multiplatform modules and dependencies (using <a href="https://github.com/icerockdev/mobile-multiplatform-gradle-plugin">mobile-multiplatform-gradle-plugin</a> you can make configuraion is simplier).<br>The project template has a sample of several features as well as.</p><h3>Use this template</h3><p>To use this template you have to go on <a href="https://github.com/icerockdev/moko-template">GitHub репозиторий шаблона moko-template</a> and press a green button Use this template. As the result, you create a new repository according to the last commit of master branch of moko-template project.</p><p>After succefull creation you should clone this rep: git clone &lt;git url of repo&gt;.</p><h3>3. Test build</h3><p>To be sure that start state is correct, will run the both applications. To do this:</p><ul><li>on Android: open root repository directory in Android Studio, wait while Gradle Sync will finish, and run android-app as regular application.</li><li>on iOS: install project’s CocoaPods (in directory ios-app run a command pod install, and after this open ios-app/ios-app.xcworkspace in Xcode and press Run for running application.</li></ul><p>Building of Kotlin/Native can take a time (it will start automatically on doing pod install as well as building iOS project).</p><h3>4. Setting up an application identifiers</h3><p>You can set an appllication identifiers like you do in regular Android and iOS application:</p><h3>Change Appliсation Id</h3><p>Android - in file android-app/build.gradle.kts need to change:</p><pre>android {<br>    ...<br><br>    defaultConfig {<br>        ...<br>        <br>        applicationId = &quot;dev.icerock.codelab.giphy&quot;<br>        ...<br>    }<br>}</pre><p>iOS - you have to set Bundle Identifier in the project&#39;s setting in Xcode like on the screenshot below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ulYPNc7ryGswrVOAXePkXA.png" /></figure><h3>Change an application name</h3><p>Android - in file android-app/src/main/res/values/strings.xml change:</p><pre>&lt;resources&gt;<br>    &lt;string name=&quot;app_name&quot;&gt;Giphy App&lt;/string&gt;<br>    ...<br>&lt;/resources&gt;</pre><p>iOS - you have to set Display name in the project&#39;s setting in Xcode like on the screenshot below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Ualyl7IPXysNU78vZoQ9Q.png" /></figure><h3>Change an application icon</h3><p>You can download the icon&#39;s resources <a href="https://codelabs.kmp.icerock.dev/codelabs/giphy-app-1/assets/giphy-1-icons.zip">here</a>.</p><p>To change Android icons you have to move content of android directory of this archive in android-app/src/main/res directory. After this, you need to set this icon on android-app/src/main/AndroidManifest.xml:</p><pre>&lt;manifest&gt;<br>    &lt;application<br>        ...<br>        android:icon=&quot;@mipmap/ic_launcher&quot;&gt;<br>        ...<br>    &lt;/application&gt;<br>&lt;/manifest&gt;</pre><p>To change icons on iOS you have to replace ios-app/src/Assets.xcassets/AppIcon.appiconset directory by the archive&#39;s version.</p><h3>Change launch screen</h3><p>There is a launch screen on iOS and to replace it you have to modify ios-app/src/Resources/LaunchScreen.storyboard file. For example, let&#39;s just change a text like on screenshot:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uMtkzzr_m5Ub9aKRmlUUHw.png" /></figure><h3>5. Next steps</h3><p>On the next lesson <a href="https://medium.com/@icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-part-2-4444ca710709">GiphyApp #2</a> we will create a Gif list.</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=2dd87d020bbd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/creating-a-simple-kotlin-multiplatform-project-based-on-moko-template-2dd87d020bbd">Creating a simple Kotlin Multiplatform project based on moko-template</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 start use Kotlin Multiplatform for mobile development]]></title>
            <link>https://medium.com/icerock/how-to-start-use-kotlin-multiplatform-for-mobile-development-1d3022742178?source=rss-2f461fccc401------2</link>
            <guid isPermaLink="false">https://medium.com/p/1d3022742178</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[kotlin-native]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Fri, 01 Nov 2019 15:40:22 GMT</pubDate>
            <atom:updated>2023-05-10T12:03:58.737Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/240/1*crrr6B5e-F7TcrZ4gNEKOA@2x.png" /></figure><h3>1. Set up Kotlin Multiplatform Project</h3><p>We are going to turn two standard projects (Android and iOS) into a multiplatform project with a shared library on Kotlin Multiplatform.</p><p>We will need:</p><ul><li>Android Studio 3.4.0+ (do not use 3.5.1 version, cause there is a <a href="https://youtrack.jetbrains.com/issue/KT-34143">bug is breaking MPP project</a>);</li><li>Xcode 10.3+;</li><li>Xcode Command Line Tools (xcode-select --install);</li><li><a href="https://cocoapods.org/">CocoaPods</a> (sudo gem install cocoapods).</li></ul><p>To start, we’ll need an Android project created from an Android Studio template and an iOS project created from an Xcode template. Put both projects in the same directory:</p><pre>├── android-app<br>│   ├── build.gradle<br>│   ├── proguard-rules.pro<br>│   └── src<br>│       ├── androidTest<br>│       │   └── java<br>│       │       └── com<br>│       │           └── icerockdev<br>│       │               └── android_app<br>│       │                   └── ExampleInstrumentedTest.kt<br>│       ├── main<br>│       │   ├── AndroidManifest.xml<br>│       │   ├── java<br>│       │   │   └── com<br>│       │   │       └── icerockdev<br>│       │   │           └── android_app<br>│       │   │               └── MainActivity.kt<br>│       │   └── res<br>│       │       ├── drawable<br>│       │       │   └── ic_launcher_background.xml<br>│       │       ├── drawable-v24<br>│       │       │   └── ic_launcher_foreground.xml<br>│       │       ├── layout<br>│       │       │   └── activity_main.xml<br>│       │       ├── mipmap-anydpi-v26<br>│       │       │   ├── ic_launcher.xml<br>│       │       │   └── ic_launcher_round.xml<br>│       │       ├── mipmap-hdpi<br>│       │       │   ├── ic_launcher.png<br>│       │       │   └── ic_launcher_round.png<br>│       │       ├── mipmap-mdpi<br>│       │       │   ├── ic_launcher.png<br>│       │       │   └── ic_launcher_round.png<br>│       │       ├── mipmap-xhdpi<br>│       │       │   ├── ic_launcher.png<br>│       │       │   └── ic_launcher_round.png<br>│       │       ├── mipmap-xxhdpi<br>│       │       │   ├── ic_launcher.png<br>│       │       │   └── ic_launcher_round.png<br>│       │       ├── mipmap-xxxhdpi<br>│       │       │   ├── ic_launcher.png<br>│       │       │   └── ic_launcher_round.png<br>│       │       └── values<br>│       │           ├── colors.xml<br>│       │           ├── strings.xml<br>│       │           └── styles.xml<br>│       └── test<br>│           └── java<br>│               └── com<br>│                   └── icerockdev<br>│                       └── android_app<br>│                           └── ExampleUnitTest.kt<br>├── build.gradle<br>├── gradle<br>│   └── wrapper<br>│       ├── gradle-wrapper.jar<br>│       └── gradle-wrapper.properties<br>├── gradle.properties<br>├── gradlew<br>├── gradlew.bat<br>├── ios-app<br>│   ├── ios-app<br>│   │   ├── AppDelegate.swift<br>│   │   ├── Assets.xcassets<br>│   │   │   ├── AppIcon.appiconset<br>│   │   │   │   └── Contents.json<br>│   │   │   └── Contents.json<br>│   │   ├── Base.lproj<br>│   │   │   ├── LaunchScreen.storyboard<br>│   │   │   └── Main.storyboard<br>│   │   ├── Info.plist<br>│   │   └── ViewController.swift<br>│   └── ios-app.xcodeproj<br>│       ├── project.pbxproj<br>│       └── project.xcworkspace<br>│           └── contents.xcworkspacedata<br>└── settings.gradle</pre><p>To do this, we create an Android project and rename the app module into android-app (remember to change the name of the module in settings.gradle too). Now, let&#39;s create an iOS project ios-app in the root directory of the Android project.</p><p>Alternatively, <a href="https://github.com/icerockdev/mobile-multiplatform-education/releases/tag/lesson-1-start">download</a> this archive with the ready setup.</p><h3>2. Create a shared library module</h3><p>To create a shared library we need to add a new gradle module (Android and mpp libraries are managed by build system <a href="https://gradle.org/">gradle</a>). To create the module:</p><p>Create an mpp-library directory (the name of our new gradle module) next to the apps to get:</p><pre>├── android-app<br>├── ios-app<br>└── mpp-library</pre><p>Create mpp-library/build.gradle. It will hold the multiplatform module configs. To begin with, the file will contain:</p><pre>apply plugin: &#39;com.android.library&#39;<br>apply plugin: &#39;org.jetbrains.kotlin.multiplatform&#39;</pre><p>Now let’s include the new module in settings.gradle:</p><pre>include &#39;:mpp-library&#39;</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/10b28e5f668f406ed4c4eb28f4240a14a01d8c58">git changes</a></p><p>After applying these changes you can run Gradle Sync and make sure that the module has connected, but can&#39;t be configured because the Android library is missing some data. First, we haven&#39;t specified Android SDK versions. Let&#39;s do this:</p><p>In mpp-library/build.gradle:</p><pre>android {<br>    compileSdkVersion 28</pre><pre>    defaultConfig {<br>        minSdkVersion 21<br>        targetSdkVersion 28<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/b3ef95a5335548fb0b5d20f8a7f8bb6ac3bd4bb1">git changes</a></p><p>This time Gradle Sync will show that it&#39;s unable to read AndroidManifest. This file is essential for any Android module.</p><p>Let’s create mpp-library/src/main/AndroidManifest.xml with the following contents:</p><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;manifest package=&quot;com.icerockdev.library&quot; /&gt;</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/638b2cbc5cad2cfc10a53a72ec4b7d94c701aa15">git changes</a></p><p>Now Gradle Sync will run successfullу.</p><h3>Setup mobile targets</h3><p>Add the following to mpp-library/build.gradle:</p><pre>kotlin {<br>    targets {<br>        android()<br>        iosArm64()<br>        iosX64()<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/2cd19fe01677f4b47fadcab8e99a701643b13273">git changes</a></p><p>Create these directories:</p><ul><li>mpp-library/src/commonMain/kotlin/</li><li>mpp-library/src/androidMain/kotlin/</li><li>mpp-library/src/iosMain/kotlin/</li></ul><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/7ea6d9b919dacf64ef16ebbe4ac48c9f51da8b5a">git changes</a></p><p>Now we can run Gradle Sync and see that the directories commonMain/kotlin and androidMain/kotlin are highlighted as the source code directories unlike the iosMain/kotlin directory, but we&#39;ll talk about it later. For now let&#39;s make sure that everything Android-related is in androidMain.</p><h3>Configure Android target</h3><p>Move AndroidManifest.xml to androidMain:<br>mpp-library/src/main/AndroidManifest.xml → mpp-library/src/androidMain/AndroidManifest.xml<br><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/67cf5b1a3743f1701f5a1c6577bacdb24d8f625b">git changes</a></p><p>However, after this change you’ll notice that Gradle Sync is once again unable to read AndroidManifest. The reason is that the Android gradle plugin is not aware of the Kotlin Multiplatform plugin. To accurately move everything Android-related to androidMain, we need to add a special configuration.</p><p>Add to mpp-library/build.gradle:</p><pre>android {<br>    <em>//...</em></pre><pre>    sourceSets {<br>        main {<br>            setRoot(&#39;src/androidMain&#39;)<br>        }<br>        release {<br>            setRoot(&#39;src/androidMainRelease&#39;)<br>        }<br>        debug {<br>            setRoot(&#39;src/androidMainDebug&#39;)<br>        }<br>        test {<br>            setRoot(&#39;src/androidUnitTest&#39;)<br>        }<br>        testRelease {<br>            setRoot(&#39;src/androidUnitTestRelease&#39;)<br>        }<br>        testDebug {<br>            setRoot(&#39;src/androidUnitTestDebug&#39;)<br>        }<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/24949ced80a8905e7bcfd2cee4c8b0617aaa519f">git changes</a></p><p>Now Gradle Sync runs successfully.</p><h3>3. Write common code</h3><p>We create mpp-library/src/commonMain/kotlin/HelloWorld.kt with the following contents:</p><pre>object HelloWorld {<br>    fun print() {<br>        println(&quot;hello common world&quot;)<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/9b174df4fb66d9448d5ebd29930c027ea7f29b09">git changes</a></p><p>However, IDE will notify you that Kotlin has not been configured. That’s because we need to hook up the kotlin stdlib library to the common (aka shared) code.</p><p>In mpp-library/build.gradle:</p><pre>kotlin {<br>    <em>// ...</em></pre><pre>    sourceSets {<br>        commonMain {<br>            dependencies {<br>                implementation &quot;org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version&quot;<br>            }<br>        }<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/748ed37ffe9b9f29ff992b609fe87f4b803ad746">git changes</a></p><p>Now IDE recognizes the Kotlin code, and we can write common code natively in Kotlin.</p><h3>4. Android app realization</h3><p>First, we need to hook up our shared library to android-app. We do it the same way as with any other Kotlin or Java module.<br>Add to android-app/build.gradle:</p><pre>dependencies {<br>    <em>// ...</em></pre><pre>    implementation project(&quot;:mpp-library&quot;)<br>}</pre><p>Then call our print function on the main screen.<br>Add to android-app/src/main/java/com/icerockdev/android_app/MainActivity.kt:</p><pre>class MainActivity : AppCompatActivity() {<br>    override fun onCreate(savedInstanceState: Bundle?) {	<br>        <em>// ...</em></pre><pre>        HelloWorld.print()<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/0d23cb4c657a097803f285bd4806df9247b0ab94">git changes</a></p><p>Then, on Gradle Sync IDE will notify you that the SDK version in android-app is lower than the SDK version in mpp-library - let&#39;s upgrade it so that we can connect the shared library (otherwise, downgrade the SDK version in the shared library).<br>In android-app/build.gradle change the minimal version of the Android SDK to make it compatible with mpp-library:</p><pre>android {<br>    <em>// ...</em><br>    minSdkVersion 21<br>    <em>// ...</em><br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/44813bfff5fb00badb0b77a1805e89fbcee6681c">git changes</a></p><p>After these changes we can run android-app on the emulator and make sure that the logcat console displays a message.</p><h3>Add Android-specific code</h3><p>Suppose, we want to use a platform-specific API. To do this, we add the platform-specific code in the shared library.</p><p>Create mpp-library/src/androidMain/kotlin/AndroidHelloWorld.kt with the following contents:</p><pre>import android.util.Log</pre><pre>object AndroidHelloWorld {<br>    fun print() {<br>        Log.v(&quot;MPP&quot;, &quot;hello android world&quot;)<br>    }<br>}</pre><p>This allows us to see another class (AndroidHelloWorld) in the Android version of the shared library, and we can use any platform functionality inside this platform-specific code (android.util.Log in our example). Now we just need to call this function in the app too.</p><p>Add in android-app/src/main/java/com/icerockdev/android_app/MainActivity.kt:</p><pre>class MainActivity : AppCompatActivity() {<br>    override fun onCreate(savedInstanceState: Bundle?) {	<br>        <em>// ...</em></pre><pre>        AndroidHelloWorld.print()<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/5f52ed8e241d960bd27c70a7880ab9f544ececc1">git changes</a></p><p>After this change we can run android-app and check that the logging works through println and through Android&#39;s Log.</p><h3>5. iOS app realization</h3><p>If you remember, IDE didn’t recognize iosMain/kotlin as a directory with the source code. That because we have initialized two targets - iosArm64Main and iosX64Main. The source code of these targets is expected in iosArm64Main/kotlin and iosX64Main/kotlin correspondingly. So we have to either duplicate the code or generalize it somehow. We recommend to use symlinks in iosMain. This approach will help us avoid the source code duplication and provide all-around correct integration with IDE.</p><p>Let’s create symlinks mpp-library/src/iosArm64Main and mpp-library/src/iosX64Main as follows:</p><pre>cd mpp-library/src<br>ln -s iosMain iosArm64Main<br>ln -s iosMain iosX64Main</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/58f5caa489700bcf2a4bdbaab5723f420f00d275">git changes</a></p><p>Now we can run Gradle Sync and notice that iosX64Main/kotlin and iosArm64Main/kotlin have become the directories with the source code.<br>Let&#39;s add the iOS-specific code for iOS, using the platform API. To do this, we can create a file in IDE through any of the created directries-symlinks (iosX64Main,iosArm64Main) - they link to the same place.</p><p>Create mpp-library/src/iosMain/kotlin/IosHelloWorld.kt:</p><pre>import platform.Foundation.NSLog</pre><pre>object IosHelloWorld {<br>    fun print() {<br>        NSLog(&quot;hello ios world&quot;)<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/43504c8598d4ccbf9ddd530e818e8c2481167986">git changes</a></p><p>Now IDE correctly recognizes iOS platform APIs, has auto-import, autocomplete, and navigation to the definition.<br>We now can compile the framework that we will connect to the iOS app. But for framework compilation we need to complete project configuration first.<br>In mpp-library/build.gradle replace iosArm64() and iosX64() with a call to the configuration block:</p><pre>kotlin {<br>    targets {<br>        <em>// ...</em></pre><pre>        def configure = {<br>            binaries {<br>                framework(&quot;MultiPlatformLibrary&quot;)<br>            }<br>        }</pre><pre>        iosArm64(&quot;iosArm64&quot;, configure)<br>        iosX64(&quot;iosX64&quot;, configure)<br>    }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/01406cfaf1ea087372cdafdcd29a73f5eff535df">git changes</a></p><p>After this change we can call Gradle Task:mpp-library:linkMultiPlatformLibraryDebugFrameworkIosX64 to compile framework for the simulator. As a result, we will get our compiled framework in the directory mpp-library/build/bin/iosX64/MultiPlatformLibraryDebugFramework/. We now need to connect it to the iOS app.</p><h3>Integrate framework into iOS app</h3><p>Open ios-app/ios-app.xcodeproj in Xcode and add framework to the project. To do this:<br>Add the framework to the project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/0*DYGpKb3rdi6V6oH5.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kKlVgO4EwRc0YN9q.png" /></figure><p>As a result, the framework should appear here:</p><p>Now we need to add the framework to embed frameworks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/662/1*ICFZeVt-9Mabheots6S_yw.gif" /></figure><p>Once that’s done, there will be duplicates in the linked frameworks. Delete one of them to get this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OtWjRuMzx6h_wLvL.png" /></figure><p>The last thing is to add the directory with the framework (./../mpp-library/build/bin/iosX64/MultiPlatformLibraryDebugFramework)to the search paths. We can do this in Build Settings of the target app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*awikeuIVvo_SbZe8.png" /></figure><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/8b4f83e18537d429dd18b2e9421649e34b762d48">git changes</a></p><p>Now we can update the view code by adding the following in ios-app/ios-app/ViewController.swift:</p><pre>import UIKit<br>import MultiPlatformLibrary</pre><pre>class ViewController: UIViewController {<br>  override func viewDidLoad() {<br>    <em>// ...</em></pre><pre>    HelloWorld().print()<br>    IosHelloWorld().print()<br>  }<br>}</pre><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/a9ab47904c0c13e9602f4c9f74a391a5efb3d05b">git changes</a></p><p>After this we can launch the iOS app in the simulator (not on device! We’ve compiled the framework for the simulator, and the settings are hard coded accordingly for now).<br>After launching the app we see that both logging variants are working and that they look different, just as with Android.</p><h3>6. Connect shared library via CocoaPods</h3><p>We can use the dependency manager from CocoaPods to integrate the shared library into the app in the most simple and convenient (for iOS developers) way. The dependency manager will help us avoid configuring numerous project settings and a separate framework compilation through Android Studio.</p><p>Here’s how CocoaPods integration works. We create a local pod that contains an embed framework (already compiled from Kotlin), and the pod itself will participate only in one phase of compilation — via a script with the call to a gradletask to compile the framework.<br>CocoaPods dictates that the framework should always be in the same place. In our configuration it&#39;s going to be in build/cocoapods/framework/MultiPlatformLibrary.framework.</p><h3>Setup framework compilation into a single directory</h3><p>Add in mpp-library/build.gradle:<br>At the beginning:</p><pre>import org.jetbrains.kotlin.gradle.plugin.mpp.Framework<br>import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink</pre><p>At the end:</p><pre>tasks.toList().forEach { task -&gt;<br>    if(!(task instanceof KotlinNativeLink)) return<br>    def framework = task.binary<br>    if(!(framework instanceof Framework)) return<br>    def linkTask = framework.linkTask</pre><pre>    def syncTaskName = linkTask.name.replaceFirst(&quot;link&quot;, &quot;sync&quot;)<br>    def syncFramework = tasks.create(syncTaskName, Sync.class) {<br>        group = &quot;cocoapods&quot;</pre><pre>        from(framework.outputDirectory)<br>        into(file(&quot;build/cocoapods/framework&quot;))<br>    }<br>    syncFramework.dependsOn(linkTask)<br>}</pre><h3>Setup local CocoaPod containing Framework</h3><p>Create mpp-library/MultiPlatformLibrary.podspec:</p><pre>Pod::Spec.new do |spec|<br>    spec.name                     = &#39;MultiPlatformLibrary&#39;<br>    spec.version                  = &#39;0.1.0&#39;<br>    spec.homepage                 = &#39;Link to a Kotlin/Native module homepage&#39;<br>    spec.source                   = { :git =&gt; &quot;Not Published&quot;, :tag =&gt; &quot;Cocoapods/#{spec.name}/#{spec.version}&quot; }<br>    spec.authors                  = &#39;IceRock Development&#39;<br>    spec.license                  = &#39;&#39;<br>    spec.summary                  = &#39;Shared code between iOS and Android&#39;</pre><pre>    spec.vendored_frameworks      = &quot;build/cocoapods/framework/#{spec.name}.framework&quot;<br>    spec.libraries                = &quot;c++&quot;<br>    spec.module_name              = &quot;#{spec.name}_umbrella&quot;</pre><pre>    spec.pod_target_xcconfig = {<br>        &#39;MPP_LIBRARY_NAME&#39; =&gt; &#39;MultiPlatformLibrary&#39;,<br>        &#39;GRADLE_TASK[sdk=iphonesimulator*][config=*ebug]&#39; =&gt; &#39;syncMultiPlatformLibraryDebugFrameworkIosX64&#39;,<br>        &#39;GRADLE_TASK[sdk=iphonesimulator*][config=*elease]&#39; =&gt; &#39;syncMultiPlatformLibraryReleaseFrameworkIosX64&#39;,<br>        &#39;GRADLE_TASK[sdk=iphoneos*][config=*ebug]&#39; =&gt; &#39;syncMultiPlatformLibraryDebugFrameworkIosArm64&#39;,<br>        &#39;GRADLE_TASK[sdk=iphoneos*][config=*elease]&#39; =&gt; &#39;syncMultiPlatformLibraryReleaseFrameworkIosArm64&#39;<br>    }</pre><pre>    spec.script_phases = [<br>        {<br>            :name =&gt; &#39;Compile Kotlin/Native&#39;,<br>            :execution_position =&gt; :before_compile,<br>            :shell_path =&gt; &#39;/bin/sh&#39;,<br>            :script =&gt; &lt;&lt;-SCRIPT<br>MPP_PROJECT_ROOT=&quot;$SRCROOT/../../mpp-library&quot;</pre><pre>&quot;$MPP_PROJECT_ROOT/../gradlew&quot; -p &quot;$MPP_PROJECT_ROOT&quot; &quot;$GRADLE_TASK&quot;<br>            SCRIPT<br>        }<br>    ]<br>end</pre><h3>Connect our local CocoaPod to the project</h3><p>Create ios-app/Podfile:</p><pre><em># ignore all warnings from all pods</em><br>inhibit_all_warnings!</pre><pre>use_frameworks!<br>platform :ios, &#39;11.0&#39;</pre><pre><em># workaround for https://github.com/CocoaPods/CocoaPods/issues/8073</em><br>install! &#39;cocoapods&#39;, :disable_input_output_paths =&gt; true</pre><pre>target &#39;ios-app&#39; do<br>  <em># MultiPlatformLibrary</em><br>  pod &#39;MultiPlatformLibrary&#39;, :path =&gt; &#39;../mpp-library&#39;<br>end</pre><p>Remove previously added FRAMEWORK_SEARCH_PATHS in the project settings. To do this, press backspace and remove the value altogether instead of editing the field by leaving it empty.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fsmyqu00o8UWQrFd.png" /></figure><p>Call pod install in the directory ios-app (<a href="https://cocoapods.org/">cocoapods</a> need to be installed prior to this).</p><p><a href="https://github.com/icerockdev/mobile-multiplatform-education/commit/1d6150d9b4ba98743fb0252ad9e40aee00141d1b">git changes</a></p><p>After successful pod install we get direct integration between Xcode and the framework (including automatic recompilation of the framework when recompiling the Xcode project). After installing the pods you should close the current Xcode project and open ios-app/ios-app.xcworkspace.<br>Now we can launch the iOS app on a device and see that it works correctly.</p><p>(!) It’s possible that when building through Xcode gradle won&#39;t run successfully and notify you that java is missing. In this case you should install <a href="https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">java development kit</a>. When gradle runs through the Android Studio, it uses and openjdk variant from the Android Studio&#39;s built-in distributive. So everything works out-of-the-box for the Android project.</p><p>That’s all. Thanks for being with us :) Join to our twitter <a href="https://twitter.com/KotlinMPP">https://twitter.com/KotlinMPP</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=1d3022742178" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/how-to-start-use-kotlin-multiplatform-for-mobile-development-1d3022742178">How to start use Kotlin Multiplatform for mobile development</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[The Rise of Multiplatform App Development: Smaller Budgets, Better Apps]]></title>
            <link>https://medium.com/icerock/the-rise-of-multiplatform-app-development-smaller-budgets-better-apps-ecb248def722?source=rss-2f461fccc401------2</link>
            <guid isPermaLink="false">https://medium.com/p/ecb248def722</guid>
            <category><![CDATA[mobile-apps]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[IceRock Development]]></dc:creator>
            <pubDate>Tue, 09 Jul 2019 16:31:24 GMT</pubDate>
            <atom:updated>2023-05-10T12:06:39.639Z</atom:updated>
            <content:encoded><![CDATA[<p>If your company is considering to build a mobile app in 2019, we have some good news. You no longer need to endlessly brainstorm which platform to target first — iOS or Android. You should be developing both versions simultaneously while spending less than you used to. And Kotlin Multiplatform is how you do this.</p><p>[Check out the links at the bottom to supply your tech-speaking colleagues with the juicy details on successfully delivering Kotlin Multiplatform projects, six real-life case studies inclusive.]</p><p>Read on to learn how your app can become multiplatform without eating into your development budget and at the same time offering your users unique, platform-specific experience.</p><h3>The usual cross-platform suspects</h3><p>When it comes to cross-platform mobile app development, the usual suspects that come to mind are tools like PhoneGap, Xamarin, Ionic, and lately also React Native with Flutter.</p><p>Their appeal has been hard to resist: a universal tool that can reduce development effort for building iOS and Android apps in parallel has been the holy grail in the app developer community for a while now.</p><p>Unfortunately, none of these technologies has managed to become the de facto instrument for building mobile apps, let alone oust native development. When working with these cross-platform app development tools, developers most often gripe about:</p><ul><li>Poor or limited UI/UX capabilities</li><li>Lack of access to platform-specific advanced features (e.g., camera and NFC)</li><li>Necessity to support three code bases: iOS, Android, and “cross-platform”</li></ul><p>But with Kotlin/Native, and more specifically Kotlin Multiplatform, things are about to change as this nascent technology promises uncompromised native-app quality while significantly optimizing the app development process.</p><h3>Multiplatform begs to differ</h3><p>Kotlin is a programming language that got official support from Google a couple of years ago and since then has become the prime tool for building Android apps natively. But its potential is much more far-reaching than mere Android apps development. The Kotlin/Native technology — a part of the Kotlin ecosystem — allows developers to target the following platforms besides Android:</p><ul><li>iOS / MacOS</li><li>Android</li><li>Windows</li><li>Linux</li><li>WebAssembly</li></ul><p>Multiplatform projects are an experimental feature in Kotlin that lets developers build iOS and Android apps at the same time using a shared codebase. This shared code base may contain the business logic, client-server pieces, and everything else that iOS and Android apps can share under the hood.</p><p>What’s important is that UI and UX remain 100% native for both iOS and Android. As a result, users get to work with apps that offer experiences similar to other best-in-class apps on each platform.</p><h3>Kotlin Multiplatform advantages for your business</h3><p>One thing you need to remember about using Multiplatform is that iOS and Android developers will still be working in their usual environment to build out UX/UI and other parts that have to do with advanced hardware functionality. The rest is handled via a shared Kotlin codebase that can and should be modular — to reuse its parts in future apps.</p><h4>Uncompromised apps quality</h4><p>iOS and Android apps that we’ve developed using Kotlin Multiplatform projects provide native UX and feature UIs that are specific to each platform. So you don’t need to worry about how your apps will look like on different devices. They will scale accordingly on different screens and will look stunning, while users will appreciate the familiar and comfortable controls.</p><h4>Faster time to market</h4><p>Kotlin is currently the primary programming language for developing Android apps. Therefore, it’s natural that an Android developer would be responsible for working on shared libraries on your multiplatform project. And an iOS developer, when working on the app, will be just plugging the Kotlin code pieces that the Android developer has prepared. This means your iOS engineer will not be spending time on developing the code that handles server interactions or the code that is responsible for the overall logic of the application or whatever else they decide to share. And as a result, the apps will hit the app stores quicker.</p><h4>Lower maintenance costs</h4><p>It’s a rare case when an app that drives real business value sits in an app store without being updated. How often do we get notifications on our phones to update another app, right? With Kotlin Multiplatform most of the time, you’ll only need to update the shared code (once) and then “apply” it to both apps, which is not the same as iOS and Android devs working separately on a new set of features for both platforms.</p><h4>Iterative approach</h4><p>The Multiplatform projects feature is also great because you don’t need to refactor (equals to developing from scratch with cross-platform solutions) your existing apps to start getting the value. Any new functionality can be added via a shared library and will cost you less during maintenance.</p><h4>Support for more platforms</h4><p>The true potential of Kotlin Multiplatform lays in how many platforms it supports besides iOS and Android: the desktop OSs such as MacOS and Windows, WebAssembly, etc. It may have a mind-blowing effect on all your business because you will be able to update your apps on all major platforms using far fewer resources than you did before.</p><h3>Kotlin Multiplatform trade-offs</h3><p>Of course, as with any new technology, there are some downsides to using Multiplatform on all app development projects.</p><h4>iOS developers will need to catch up</h4><p>While Android developers feel completely at home with Kotlin, iOS app developers will need to learn a few things to be able to contribute to developing shared code. One thing that probably makes the whole idea less scary is that Kotlin resembles Swift in many ways: It’s a modern, well-documented programming language with similar syntax and excellent tooling. So the learning curve won’t be that steep.</p><h4>Android vs. iOS architectures</h4><p>iOS and Android developers will need to coordinate their approach to the architecture of the future apps, as a shared codebase implies that this architecture will be practically identical on both platforms. This certainly implies some quality time at the start of the project for iOS and Android developers working in tandem.</p><h4>Experimental feature</h4><p>Kotlin developers are very explicit about their commitment to the Kotlin Multiplatform functionality, but they also plainly state that the feature is experimental. For app developers, this means that anything can change any time, and this is something they should be ready for.</p><h3>Got questions about Multiplatform?</h3><p>At <a href="http://www.icerockdev.com">IceRock</a>, we have been investing in Multiplatform a lot because we see the future of mobile development with one universal environment for developing apps on all platforms in parallel.</p><p>As of June 2019, we’ve already helped our clients release six apps to the app stores and keep on honing our Kotlin Multiplatform skills, which allows us to offer more appealing development budgets and delivery timelines. For more technical details please read:</p><p><a href="https://blog.usejournal.com/the-dos-and-donts-of-mobile-development-with-kotlin-multiplatform-db7c098545c0"><strong>The Dos and Dont’s of Mobile Development with Kotlin Multiplatform: Part I</strong></a></p><p><a href="https://blog.usejournal.com/the-dos-and-donts-of-mobile-development-with-kotlin-multiplatform-part-ii-d318dae8475b"><strong>The Dos and Dont’s of Mobile Development with Kotlin Multiplatform: Part II</strong></a></p><p>Please <a href="https://icerockdev.com/#contacts">get in touch</a> if you have questions about leveraging the power of Multiplatform on your app projects.</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=ecb248def722" width="1" height="1" alt=""><hr><p><a href="https://medium.com/icerock/the-rise-of-multiplatform-app-development-smaller-budgets-better-apps-ecb248def722">The Rise of Multiplatform App Development: Smaller Budgets, Better Apps</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>