<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gustavo Fão Valvassori</title>
    <description>The latest articles on DEV Community by Gustavo Fão Valvassori (@faogustavo).</description>
    <link>https://dev.to/faogustavo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F242261%2F71dedf8d-a3b6-4041-81e5-f0b871151a0a.jpeg</url>
      <title>DEV Community: Gustavo Fão Valvassori</title>
      <link>https://dev.to/faogustavo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faogustavo"/>
    <language>en</language>
    <item>
      <title>Amper: a new way to configure Gradle projects?</title>
      <dc:creator>Gustavo Fão Valvassori</dc:creator>
      <pubDate>Fri, 08 Dec 2023 12:05:34 +0000</pubDate>
      <link>https://dev.to/touchlab/amper-a-new-way-to-configure-gradle-projects-3abf</link>
      <guid>https://dev.to/touchlab/amper-a-new-way-to-configure-gradle-projects-3abf</guid>
      <description>&lt;p&gt;Earlier this month, Kotlin Multiplatform went stable. And just a couple of weeks later, they announced a new tool to help with one of the biggest issues we have, configuration. Currently, KMP projects use Gradle as a Build System, and we must agree that it's a great tool. But the complexity around it for new users is a big issue.&lt;/p&gt;

&lt;p&gt;Amper is a tool to help you configure your projects using a markup language. The main goal is to replace the Groovy/Kotlin programming language with something that can enforce a declarative approach. With a declarative paradigm,  you can only declare what needs to be done without implementing it. If you already have experience with Maven, it may be familiar to you.&lt;/p&gt;

&lt;p&gt;Please note that the tool is in the early stages of development, and its goal is to validate the idea before putting more effort into it. With that in mind, be aware that syntax, language, and other things may change in the future, and it’s a very experimental tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;According to the docs, &lt;code&gt;Amper is implemented as a Gradle plugin and uses YAML for its project configuration format&lt;/code&gt;. In other words, it's still a Gradle environment, but it will use an additional YAML file to configure the project. To be able to use it, you need one of the following environments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use one of the following IDEs:

&lt;ol&gt;
&lt;li&gt;IntelliJ IDEA 2023.3 or newer and the Amper Plugin; or&lt;/li&gt;
&lt;li&gt;Fleet 1.26 or newer;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Use the Amper build plugin to enable it:

&lt;ol&gt;
&lt;li&gt;In your &lt;code&gt;settings.gradle.kts&lt;/code&gt;, add this plugin: &lt;code&gt;id("org.jetbrains.amper.settings.plugin").version("0.1.1")&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you open a project with the Amper Plugin in the IDE, it will look for the &lt;code&gt;module.yaml&lt;/code&gt; file. This file will contain the configuration for the module (library or application), dependencies, and other project configurations (like the Java version).&lt;/p&gt;

&lt;p&gt;Also worth noting that it also changes the project architecture. Instead of having a directory for your source set artifacts, you will have multiple &lt;code&gt;src&lt;/code&gt; directories. In other words, your &lt;code&gt;commonMain/src/kotlin&lt;/code&gt; will be only &lt;code&gt;src&lt;/code&gt; now, and your &lt;code&gt;iosMain/src/kotlin&lt;/code&gt; will be only &lt;code&gt;src@ios&lt;/code&gt;. This is a bit confusing at first, but it helps flatten the project structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sUhLP2iw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/v1/./img.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sUhLP2iw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/v1/./img.png" alt="Amper Project Example" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Thoughts
&lt;/h2&gt;

&lt;p&gt;Amper comes to fix a huge problem for all KMP developers. We at Touchlab look forward to anything that will reduce the configuration complexity in KMP, and Amper is hopefully a step in the right direction. The Gradle team &lt;a href="https://blog.gradle.org/declarative-gradle"&gt;seems to be on board&lt;/a&gt;, at least with the concept. Hopefully the combined efforts will result in something that is truly easier to use and more maintainable.&lt;/p&gt;

&lt;p&gt;The integration with the IDEs works fine for configuration. It knows the configuration schema and gives you valid suggestions for properties. Unfortunately, the configuration of the third-party plugins is not currently supported, but it's on their roadmap.&lt;/p&gt;

&lt;p&gt;The project APIs are small and only supports a short range of configurations. For basic contexts it works like a charm, but when you need to do something more complex, you may need to work with Gradle again.&lt;/p&gt;

&lt;p&gt;The Gradle interop is great. You can still have a &lt;code&gt;build.gradle.kts&lt;/code&gt; file to make custom configurations that are not available yet in the YAML file. One does not replace the other, but complement instead. This will be useful if you need to create intermediate source-sets, configure plugins or even import libraries that have a different configuration.&lt;/p&gt;

&lt;p&gt;For now, we recommend testing it it out on smaller projects that may not have a big impact with future changes, as it's still experimental. But we are excited to see how it will evolve in the future. If you are using it, don't forget to share your opinions with the Jetbrains team on their &lt;a href="https://surveys.jetbrains.com/s3/Amper-feedback-form"&gt;Feedback Form&lt;/a&gt; and their &lt;a href="https://kotlinlang.slack.com/archives/C062WG3A7T8"&gt;Slack Channel&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.jetbrains.com/blog/2023/11/09/amper-improving-the-build-tooling-user-experience/"&gt;Official Announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-tooling-in-fleet/"&gt;Welcome Fleet with Kotlin Multiplatform Tooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JetBrains/amper/"&gt;Github Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surveys.jetbrains.com/s3/Amper-feedback-form"&gt;Feedback Form&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Getting Started with Compose for Web</title>
      <dc:creator>Gustavo Fão Valvassori</dc:creator>
      <pubDate>Fri, 01 Dec 2023 21:34:32 +0000</pubDate>
      <link>https://dev.to/touchlab/getting-started-with-compose-for-web-455n</link>
      <guid>https://dev.to/touchlab/getting-started-with-compose-for-web-455n</guid>
      <description>&lt;p&gt;If you are a Kotlin developer like me, you are probably very excited about Jetpack Compose. It’s a modern UI toolkit for developing apps, and with the great work from JetBrains, it’s becoming a really interesting solution.&lt;/p&gt;

&lt;p&gt;As a mobile developer, I’ve never gone in the weeds on how to properly develop sites with HTML, CSS, and JS. My closest experience was with React Native back in 2018. But now, with Compose, using a Kotlin DSL, I feel comfortable on doing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose Runtime and Component Library
&lt;/h2&gt;

&lt;p&gt;Before creating a project and start coding, it's important to have a small introduction to Compose runtimes. As of now, Compose has two different runtimes on the web:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JS + HTML&lt;/li&gt;
&lt;li&gt;Wasm + Canvas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They both use the Compose Runtime underneath, but they use different components. Long story short: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The HTML version will have HTML composable components (like H1, P, Span, Input, Div, etc.) &lt;/li&gt;
&lt;li&gt;The Canvas version will have similar components as the Android version as it uses SKIA to draw on the Canvas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of them are experimental. Note that choosing how you want to build your application will impact on possible code-sharing with native if this is what you want.&lt;/p&gt;

&lt;p&gt;For this series of posts, I'll use the HTML version and I will be able to explore more the web components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the project
&lt;/h2&gt;

&lt;p&gt;Now that we know what runtime/components we want to use, we need to create a project. My first idea was to use the &lt;a href="https://kmp.jetbrains.com" rel="noopener noreferrer"&gt;KMP project wizard&lt;/a&gt;, but unfortunately, the Web target is not available as an option for now. As a workaround, I had to create a new clean project using IntelliJ IDEA and add the dependencies manually. &lt;/p&gt;

&lt;p&gt;Using a clean template you will reduce the amount of code that needs to be removed/updated to make the project work. But if you prefer to use any other IDE/Template, feel free to do so and share your experience with us later :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feya3ykaxbvtitksa51ff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feya3ykaxbvtitksa51ff.png" alt="IntelliJ IDEA Project Wizard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, the configuration is completed with a few extra lines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the Kotlin plugin from JVM to MULTIPLATFORM&lt;/li&gt;
&lt;li&gt;Add the Compose plugin&lt;/li&gt;
&lt;li&gt;Declare JS source-sets&lt;/li&gt;
&lt;li&gt;Import the dependencies&lt;/li&gt;
&lt;li&gt;Create an HTML file&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"multiplatform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"1.9.20"&lt;/span&gt; &lt;span class="c1"&gt;// Make this multiplatform&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.compose"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"1.5.10"&lt;/span&gt; &lt;span class="c1"&gt;// Add compose plugin&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;js&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Declare JS source-sets&lt;/span&gt;
        &lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;binaries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;sourceSets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;jsMain&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;getting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="c1"&gt;// Import libraries&lt;/span&gt;
                &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;jsTest&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;getting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-js"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you have a trained eye for Gradle configuration, you probably already noticed that we are importing the Compose HTML library. And that's why we need to create an HTML page that will load and be filled by our Composable code. This file should be located at &lt;code&gt;jsMain/resources/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Compose Web&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"compose-sweeper.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This can be very simple, just load the JS built from your Kotlin code and create a DOM element that will be you compose root.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the project
&lt;/h2&gt;

&lt;p&gt;As with any other project using Gradle as the build system, you will run most of your changes with the &lt;code&gt;gradlew&lt;/code&gt; binary (or the &lt;code&gt;gradlew.bat&lt;/code&gt; if you are running on Windows). Even though there are multiple commands listed when you call &lt;code&gt;gradlew tasks&lt;/code&gt;, the important ones are three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;./gradlew jsBrowserDevelopmentRun&lt;/code&gt; - Start development server;

&lt;ol&gt;
&lt;li&gt;This call supports the &lt;code&gt;--continuous&lt;/code&gt; build flag that can be used for reloading the app when the code changes;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;./gradlew jsBrowserProductionWebpack&lt;/code&gt; - Generate the bundle version of your project. 

&lt;ol&gt;
&lt;li&gt;It will spit out a static site at the &lt;code&gt;build/dist/js/productionExecutable&lt;/code&gt; dir;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;./gradlew allTests&lt;/code&gt; - Run all unit tests you have implemented for your project;&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  How does this work?
&lt;/h2&gt;

&lt;p&gt;When you start the development server, it will compile your Kotlin code and run the &lt;code&gt;main()&lt;/code&gt; function. On this function, you can call the &lt;code&gt;renderComposable&lt;/code&gt; method to start a composable block inside a window element.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;renderComposable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootElementId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, it will render all the content inside one root element we've defined on previous sections.&lt;/p&gt;

&lt;p&gt;Lastly, as you can see at the end of the body block, we are importing the JS file created while building our application. The default name is just the project name with a &lt;code&gt;.js&lt;/code&gt; extension.&lt;/p&gt;

&lt;p&gt;Now that we have everything in place, we can finally run our server and have the following result:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In this first article, we are just exploring how to create and set up your project. But we can already say that Compose Web is a promising alternative for Kotlin developers who don’t want to learn HTML/CSS/JS (even though you will need it). &lt;/p&gt;

&lt;p&gt;In future posts, we will dig into more topics, like how to use HTML Components, Styling with CSS, Handling, and Listening to DOM events. After all the articles, you will be able to fully understand how I’ve built &lt;a href="https://faogustavo.github.io/compose-sweeper/" rel="noopener noreferrer"&gt;this Minesweeper game&lt;/a&gt; only using Compose.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>compose</category>
      <category>multiplatform</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Understanding and Configuring your Kotlin Multiplatform Mobile Test Suite</title>
      <dc:creator>Gustavo Fão Valvassori</dc:creator>
      <pubDate>Wed, 12 Jan 2022 00:04:00 +0000</pubDate>
      <link>https://dev.to/touchlab/understanding-and-configuring-your-kotlin-multiplatform-mobile-test-suite-1i4f</link>
      <guid>https://dev.to/touchlab/understanding-and-configuring-your-kotlin-multiplatform-mobile-test-suite-1i4f</guid>
      <description>&lt;p&gt;Writing tests is part of every developer's day-to-day routine. They help you write better and more reliable code. In addition, they can verify that your code does what it was supposed to do, and your changes haven't introduced bugs. In this article, I'll show you how to configure the test suite from your KMM project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding a test suite
&lt;/h2&gt;

&lt;p&gt;Before configuring the test suite, we need to understand the different testing environments in the mobile ecosystem.&lt;/p&gt;

&lt;p&gt;On Android, we have two different test suites. They can be Unit Tests and run in the JVM without the Android Framework or Instrumented Tests (having access to the framework) running in an Android device or emulator. To handle these different types of tests, you have different source sets on your project.&lt;/p&gt;

&lt;p&gt;On the iOS side, things are more straightforward. Here, you can implement both Unit Tests and Instrumented Tests in the same target. That is allowed because all of them will run on the iOS Simulator or a physical device (You can change that, but by default, it's how things work). On iOS, the difference between Unit and Instrumentation is in the concept, while in Android, it's in where your code is and how you run them.&lt;/p&gt;

&lt;p&gt;To understand a Unit Test or an Instrumented Test, we need to discuss the test pyramid briefly. It's a visual representation of the difference between the test types (or levels) described by Mike Cohn in the book "Succeeding with agile."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3az7737algzznn3jtmdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3az7737algzznn3jtmdt.png" alt="Image from The Practical Test Pyramid by Ham Vocke"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I do not intend to go deep into the meaning of each level. If you want a better explanation, take a look at this blog post from Ham Vocke. But to help your understanding, here is their definition in short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt;: This type tries to test your software in the tiniest portion (or unit) that they can. When you implement unit tests, you will probably use mocks and stubs to provide these unit dependencies, creating a controlled environment. So, you can think of a Unit as a class or function on your project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Tests&lt;/strong&gt;: Most projects will have at least one integration. It can be an SQLite database to store data or an HTTP client to fetch network data. The service test is responsible for testing these types of integration. When you are also testing your integration with the platform (using some class/method provided by the Android/iOS SDK), you are also writing integration tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Test&lt;/strong&gt;: Last but not least, this type is responsible for testing your UI. Here, you will implement code that will perform actions on your project (like clicking, typing, swiping, and other operations). You can also implement Snapshot Tests to guarantee that all screens are pixel-perfect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When considering how many of each type to write, keep in mind that each project will have different needs, but service tests run faster than UI tests, and unit tests are faster than both. In other words, opt to have more tests in the lower part of the pyramid and less in the top part&lt;/p&gt;

&lt;p&gt;Deciding which one you should implement will depend on your requirements. But you can use this rule for most cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want to test &lt;strong&gt;business logic&lt;/strong&gt; isolated, create Unit Tests;&lt;/li&gt;
&lt;li&gt;If you're going to test the &lt;strong&gt;integration of more than one component&lt;/strong&gt; (from your project or the platform framework), create Integration Tests;&lt;/li&gt;
&lt;li&gt;If you are going to test your &lt;strong&gt;UI itself&lt;/strong&gt; or some UI behavior, implement UI Tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding the KMM Test Suite
&lt;/h2&gt;

&lt;p&gt;First of all, let's review a KMM project created by the &lt;a href="https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile" rel="noopener noreferrer"&gt;Jetbrains KMM plugin&lt;/a&gt;. It is composed of three different types of projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first one is an Android Application inside the &lt;code&gt;androidApp&lt;/code&gt; folder;&lt;/li&gt;
&lt;li&gt;the next one is an iOS Application inside the &lt;code&gt;iosApp&lt;/code&gt; folder;&lt;/li&gt;
&lt;li&gt;and last, we have a KMP library inside the &lt;code&gt;shared&lt;/code&gt; folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdx3yt0q1s1sb7x0k2y3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdx3yt0q1s1sb7x0k2y3.png" alt="Basic KMM project structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This image represents the basic structure, and you will probably have something that looks like this. With that in mind, we now know that there are three different types of testing configurations that we need to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Android test suite;&lt;/li&gt;
&lt;li&gt;The iOS test suite;&lt;/li&gt;
&lt;li&gt;The Shared test suite;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring your Android Suite
&lt;/h2&gt;

&lt;p&gt;As we discussed earlier, on Android, you can have Instrumented Tests (that requires an emulator) and Unit Tests (that will run in the JVM on your machine). The android project will have two different directories for that. So, inside your &lt;code&gt;androidApp/src&lt;/code&gt; dir, you will have the &lt;code&gt;androidTest&lt;/code&gt; for instrumented tests and the &lt;code&gt;test&lt;/code&gt; folder for Unit tests.&lt;/p&gt;

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

&lt;p&gt;First, we need to add some dependencies that the KMM plugin doesn't include as default. For the Unit Tests, we will require just the JUnit. For the Instrumented tests, we will need the JUnit Extensions and the Espresso testing library. Adds this to your &lt;code&gt;androidApp/build.gradle&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// All other dependencies from your application&lt;/span&gt;
    &lt;span class="n"&gt;testImplementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"junit:junit:4.13.2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.ext:junit:1.1.3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.ext:junit-ktx:1.1.3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.espresso:espresso-core:3.4.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also have to define your test instrumentation runner. For this, also add this line to your grade file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;defaultConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// All other app configs&lt;/span&gt;

        &lt;span class="c1"&gt;// Add this&lt;/span&gt;
        &lt;span class="n"&gt;testInstrumentationRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"androidx.test.runner.AndroidJUnitRunner"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After syncing the project, we can finally implement our tests. Let's start with UnitTests. Here I'm going to create an &lt;code&gt;ExampleUnitTest&lt;/code&gt; class inside the &lt;code&gt;test/kotlin/path/to/your/package/&lt;/code&gt;. In that, I'll check if the platform name returned from the shared code is what we expect&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleUnitTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sut&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;platform_returnsCorrectName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;
        &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Android"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this test, you can run this command on your terminal &lt;code&gt;./gradlew :androidApp:testDebugUnitTest&lt;/code&gt;, or use the play button on the side of your test class name. If everything is okay, you will receive a "Build Successful" message.&lt;/p&gt;

&lt;p&gt;For the Instrumented Tests, it's pretty similar, but instead of testing a method, we will check if our code is rendering the label with the correct text. To do that, we need to create our test inside the &lt;code&gt;androidTest/kotlin/path/to/your/package/&lt;/code&gt;. For this test, I'll name it as &lt;code&gt;ExampleInstrumentedTest&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidJUnit4&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleInstrumentedTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;activityScenarioRule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activityScenarioRule&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MainActivity&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;textView_rendersHelloAndroid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Espresso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_view&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ViewAssertions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDisplayed&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ViewAssertions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, Android!"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process for running Instrumentation tests is very similar to Unit tests, but before running, remember to start an emulator (or connect a device) and wait for it to complete the boot. Otherwise, your tests are going to fail. The command for running in the terminal is &lt;code&gt;./gradlew :androidApp:connectedCheck&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the basic Android setup. For more details about testing an Android Application, check out the &lt;a href="https://developer.android.com/studio/test" rel="noopener noreferrer"&gt;official android testing documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring your iOS Suite
&lt;/h2&gt;

&lt;p&gt;To configure the iOS test suite, we need to open the project in the XCode. After opening your project, click on the root node from your project on the left, and you will see that your project has only one target.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ha0nqhp69ai4y4ezpm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ha0nqhp69ai4y4ezpm8.png" alt="Adding iOS test target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add the test target, click on the + button at the bottom, search for the Unit Testing Bundle, and add to your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2mqko1hvxfvwhtytils.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2mqko1hvxfvwhtytils.gif" alt="Adding iOS Test Target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating it, you will have a new target, and you will notice a new group on your project navigator on the left. It's probably named iosAppTests, but this can change according to what you defined while creating the testing bundle.&lt;/p&gt;

&lt;p&gt;With the testing target created, link the shared module to our test target. In this project, I'm using Cocoapods, and to add this dependency, edit your Podfile. Inside the iosApp target, add the iosAppTests target. In the end, it should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="s1"&gt;'iosApp'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;use_frameworks!&lt;/span&gt;
  &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="ss"&gt;:ios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'14.1'&lt;/span&gt;
  &lt;span class="n"&gt;pod&lt;/span&gt; &lt;span class="s1"&gt;'shared'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'../shared'&lt;/span&gt;

  &lt;span class="c1"&gt;# Add this&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="s1"&gt;'iosAppTests'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;inherit!&lt;/span&gt; &lt;span class="ss"&gt;:search_paths&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, quit Xcode, run &lt;code&gt;pod install&lt;/code&gt; inside your project, and open it again. With that ready, we can start writing some unit tests.&lt;/p&gt;

&lt;p&gt;First, right-click in this new group, and select &lt;code&gt;New File&lt;/code&gt;. It will prompt you with a box for choosing which file template you want. Select &lt;code&gt;Unit Test Case Class&lt;/code&gt;. We can name it &lt;code&gt;ExampleUnitTest&lt;/code&gt; to reproduce what we did on the Android side. In the final step, Xcode will launch a prompt to select the saving location. For now, you can save it in this same folder. Just make sure that the &lt;code&gt;iosAppTests&lt;/code&gt; is selected in the targets list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33uztt7hstn3fb876fxb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33uztt7hstn3fb876fxb.gif" alt="Creating test class"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can delete all that boilerplate code inside the class. Add a testable import from the shared module, create the subject under test, and a testing function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;XCTest&lt;/span&gt;
&lt;span class="kd"&gt;@testable&lt;/span&gt; &lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ExampleUnitTest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testPlatform_returnsCorrectName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;
        &lt;span class="kt"&gt;XCTAssertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"iOS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this test, you can click on the "diamond" icon on the side of the class name or run the following command on your terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xcodebuild \
  -workspace iosApp.xcworkspace \
  -scheme iosApp \
  -sdk iphonesimulator \
  -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.0' \
  build test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For UI Tests, you need to add another target. Search, and add the UI Testing Bundle to your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75hvd7g35zp3jpk3kme3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75hvd7g35zp3jpk3kme3.gif" alt="Adding iOS UI Test Target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then, add the target to your Podfile, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="s1"&gt;'iosApp'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;use_frameworks!&lt;/span&gt;
  &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="ss"&gt;:ios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'14.1'&lt;/span&gt;
  &lt;span class="n"&gt;pod&lt;/span&gt; &lt;span class="s1"&gt;'shared'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'../shared'&lt;/span&gt;

  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="s1"&gt;'iosAppTests'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;inherit!&lt;/span&gt; &lt;span class="ss"&gt;:search_paths&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Add this&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="s1"&gt;'iosAppUITests'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;inherit!&lt;/span&gt; &lt;span class="ss"&gt;:search_paths&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process is very similar to what we've done for the Unit Test to create a UI test, but this time we will add a &lt;code&gt;UI Test Case Class&lt;/code&gt;. The name can be &lt;code&gt;ExampleUITest&lt;/code&gt;, and we will do the same as we did on Android Instrumented Tests. Don't forget to check the target at the end of the creation. It should have the iosAppUITests selected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawfp5uhml5fyhuows06z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawfp5uhml5fyhuows06z.gif" alt="Creating UI Test class"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you can delete everything except the &lt;code&gt;setupWithError&lt;/code&gt;. It has some code responsible for launching the application under tests and stopping the test suite if some error was found in this class. Knowing that, let's start working on the test. We will reproduce what we did on the Android side and check if the text &lt;code&gt;Hello, iOS!&lt;/code&gt; is present. Here is the code for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;XCTest&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ExampleUITest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;lazy&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;XCUIApplication&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setUpWithError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;continueAfterFailure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testApplication_shouldRenderHelloText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;staticTexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Hello, iOS!"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="kt"&gt;XCTAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the tests, the process is the same as Unit Tests. You can run from the diamond icon or with the same command line command.&lt;/p&gt;

&lt;p&gt;For more details about iOS testing, check the &lt;a href="https://developer.apple.com/documentation/xcode/testing-your-apps-in-xcode" rel="noopener noreferrer"&gt;official docs page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring your Shared Test Suite
&lt;/h2&gt;

&lt;p&gt;Finally, we will set up tests for the shared module. This module is a mix of what we've seen before. The KMP project created by the KMM plugin is composed of two different platforms (Android + iOS) and a shared source set that allows you to write (and test) code that works on both platforms. This module will have a lot of source sets. It will have the implementation folders (&lt;code&gt;commonMain&lt;/code&gt;, &lt;code&gt;androidMain&lt;/code&gt;, and &lt;code&gt;iosMain&lt;/code&gt;) and at least one test folder (&lt;code&gt;commonTest&lt;/code&gt;, &lt;code&gt;androidTest&lt;/code&gt;, and &lt;code&gt;iosTest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Be aware that we have a small trap. As I've mentioned before, Android tests can be divided into Integrated and Unit tests. The same still happens in the shared module. For example, an &lt;strong&gt;Android project&lt;/strong&gt; lets you implement the &lt;em&gt;integration tests&lt;/em&gt; inside the &lt;code&gt;androidTest&lt;/code&gt; folder, but in the &lt;strong&gt;KMP project&lt;/strong&gt;, this folder is for &lt;em&gt;unit tests&lt;/em&gt;. Therefore, we should implement android instrumentation tests inside the &lt;code&gt;androidAndroidTest&lt;/code&gt; source set. This is only required if you need access to the context or any other Android SDK class.&lt;/p&gt;

&lt;p&gt;Another quick thing that I need to mention is that we will not require a simulator for the iOS tests. Here, your tests will run directly on your machine, but you will not lose access to the platform classes. So everything should still work fine.&lt;/p&gt;

&lt;p&gt;To start writing tests, if you hadn't checked the "Add shared tests" checkbox while creating a new project, you would need to add the testing dependencies to the shared module. Here they are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;sourceSets&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;commonTest&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test-common"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test-annotations-common"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;androidTest&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test-junit"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"junit:junit:4.13.2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;androidAndroidTest&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.ext:junit:1.1.3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.ext:junit-ktx:1.1.3"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"androidx.test.espresso:espresso-core:3.4.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After syncing the project, you should be good to go. Let's start writing some tests. To make things quick here, we will use the default Greeting class from the project setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, ${Platform().platform}!"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Shared tests, we can check if the Hello is mentioned, as it is the same for both parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommonGreetingTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;"Check 'Hello' is mentioned"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the Android tests, we can check if the Android name is mentioned in the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AndroidGreetingTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Android"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;"Check Android is mentioned"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iOS is the same as Android. We check that the platform name was mentioned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IosGreetingTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"iOS"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;"Check iOS is mentioned"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that all tests are implemented, we can run them. You can run using the IDE button on the side of each class name or using gradlew from the command line. The command is ./gradlew :shared:allTests. If the build succeeds, all tests have passed. If it fails, there are some issues with them.&lt;/p&gt;

&lt;p&gt;The tricky part comes next, the Instrumentation Tests. As I said before, if your subject needs access to the context (or any SDK class) on Android, you will have to implement it on the androidAndroidTest source set. If you implement them on the androidTest source set, you will receive a Runtime Exception.&lt;/p&gt;

&lt;p&gt;As we don't have any android instrumentation tests for this project, I implement a test to ensure that the context is available and that the SDK classes work correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidJUnit4&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleInstrumentedTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;useAppContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;appContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InstrumentationRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstrumentation&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;targetContext&lt;/span&gt;
        &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"dev.valvassori.kmp.test.test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;appContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packageName&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;parseUri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://google.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command is the same as for the Android app: &lt;code&gt;./gradlew :shared:connectedCheck&lt;/code&gt; and will require the android emulator to run instrumented tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;KMM projects have a complete and robust test suite. You can have Unit and Integration tests for both the shared module and the platform code. There are some tricky parts, like the naming in the common code, but now that you know them, it will be easy. Last, If you want to configure a CI environment for your project, all required build and test commands are mentioned here in this article.&lt;/p&gt;

&lt;p&gt;If you want to see the whole project, it's available in &lt;a href="https://github.com/touchlab-lab/kmp-test" rel="noopener noreferrer"&gt;this Github project&lt;/a&gt;. Check it out if you need to review something.&lt;/p&gt;




&lt;p&gt;Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at &lt;a href="https://dev.to/faogustavo"&gt;@faogustavo&lt;/a&gt; on &lt;a href="https://twitter.com/faogustavo" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, the &lt;a href="https://slack.kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin Slack&lt;/a&gt;, or &lt;a href="http://slack.androiddevbr.org/" rel="noopener noreferrer"&gt;AndroidDevBr Slack&lt;/a&gt;. And if you find all this interesting, maybe you'd like to &lt;a href="https://touchlab.co/contact-us/" rel="noopener noreferrer"&gt;work with&lt;/a&gt; or &lt;a href="https://touchlab.co/careers-3/" rel="noopener noreferrer"&gt;work at&lt;/a&gt; Touchlab.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>testing</category>
      <category>kmm</category>
    </item>
    <item>
      <title>Compose Animations beyond the state change</title>
      <dc:creator>Gustavo Fão Valvassori</dc:creator>
      <pubDate>Tue, 24 Aug 2021 17:14:23 +0000</pubDate>
      <link>https://dev.to/touchlab/compose-animations-beyond-the-state-change-234a</link>
      <guid>https://dev.to/touchlab/compose-animations-beyond-the-state-change-234a</guid>
      <description>&lt;p&gt;Jetpack Compose hit 1.0 a few weeks ago, and it came with a wonderful and robust animations API. With this new API, you will have total control over animations when the state changes. But when it comes to more complex scenarios, you may face some non-obvious paths. This article will explore some of these scenarios and help you understand how you can achieve your goal.&lt;/p&gt;

&lt;p&gt;I'm about to discuss the problems I found when trying to implement the &lt;a href="https://github.com/yonglan-whl/AVLoadingIndicatorView" rel="noopener noreferrer"&gt;AVLoadingIndicatorView&lt;/a&gt; library in Compose. And to guide you through this journey, I'll use the following loading indicators as examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qrttsqqftz8zuch0gr5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qrttsqqftz8zuch0gr5.gif" alt="Kapture 2021-08-18 at 18.02.33"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Also, all the code we discuss in this article is available in this repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/faogustavo" rel="noopener noreferrer"&gt;
        faogustavo
      &lt;/a&gt; / &lt;a href="https://github.com/faogustavo/loading-indicator-demo" rel="noopener noreferrer"&gt;
        loading-indicator-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  BallScaleIndicator
&lt;/h2&gt;

&lt;p&gt;Let's start with some simple animation. The animation consists in reducing alpha while increasing the scale from a circle. We can do that with just one value that will move from 0 to 1. So the scale will be the current value, and the alpha will be the complementary value (&lt;code&gt;1 - currentValue&lt;/code&gt;). As this is a loading animation, we will have to configure it to repeat forever.&lt;/p&gt;

&lt;p&gt;TL;DR; we would have to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;BallScaleIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationProgress&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;animateFloatAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But when you run your app, this is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y6vxpgvpn2nd0w6iris.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y6vxpgvpn2nd0w6iris.gif" alt="Loading indicator not working"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing happens. Why? In the first lines from this article, we said that Compose has an awesome API to animate state changes, but this is not some state change. We would have to add one variable to control the target state and change it to start the animation. Also, when the view is rendered, we would have to change the value to start the animation.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;BallScaleIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create one target value state that will &lt;/span&gt;
    &lt;span class="c1"&gt;// change to start the animation&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;targetValue&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Update the attribute on the animation&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationProgress&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;animateFloatAsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Use the SideEffect helper to run something&lt;/span&gt;
    &lt;span class="c1"&gt;// when this block runs&lt;/span&gt;
    &lt;span class="nc"&gt;SideEffect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And now, everything works 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryhruwmikkgk87yj2b1t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryhruwmikkgk87yj2b1t.gif" alt="Loading indicator with side effect"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;But we have another problem now. The &lt;code&gt;SideEffect&lt;/code&gt; will run every time the view is recomposed. So as the animation changes the state value on each iteration, we will run this effect many times. It makes the animation work, but it may not scale nicely and cause some UI issues. &lt;/p&gt;

&lt;p&gt;Compose also provides a way to animate values using transitions. One type of transition is the InfiniteTransition. It provides you a syntax with the initial and final values as parameters and it's automatically started when created (no need for SideEffects). To use it, you need to create an instance of it using the &lt;code&gt;rememberInfiniteTransition&lt;/code&gt; method and call the &lt;code&gt;animateFloat&lt;/code&gt; function to have an animated state.&lt;/p&gt;

&lt;p&gt;After a small refactor, this is the result.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;BallScaleIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Creates the infinite transition&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;infiniteTransition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberInfiniteTransition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Animate from 0f to 1f&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationProgress&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;infiniteTransition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animateFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;animationProgress&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And the result animation is the same, but without using side effects on this function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgbtq690srggzutyze7p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgbtq690srggzutyze7p.gif" alt="Loading indicator with infinite transition"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  BallPulseSyncIndicator
&lt;/h2&gt;

&lt;p&gt;Alright, we got the first one. Let's try something more complex. This one consists of three balls jumping in synchrony. The easiest way to achieve this is to delay the start of each animation. We can have an animation time of 600ms and start it with a delay of 70ms for each ball.&lt;/p&gt;

&lt;p&gt;On a quick search into the compose animation API, we find that the &lt;code&gt;tween&lt;/code&gt; animation has a property &lt;code&gt;delayMillis&lt;/code&gt; that we can use to implement this behavior. And to animate the values, we can keep the InfiniteTransition. So let's start working with it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;BallPulseSyncIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;infiniteTransition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberInfiniteTransition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;infiniteTransition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animateFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;delayMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;repeatMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RepeatMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;animationValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;animatedValue&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animatedValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sound good, right? But when we see the animation, we will notice something weird.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yjut01re4h206h1eq37.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yjut01re4h206h1eq37.gif" alt="Loading indicator losing synchrony"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that, after some running time, the animation loses synchrony and starts behaving weirdly. The reason for that is the property that we used to delay the animation. It applies the delay to each iteration, not just the first one.&lt;/p&gt;

&lt;p&gt;The solution to this is to use the Coroutines Animation API. It's provided by the Compose animations and has a method called &lt;code&gt;animate&lt;/code&gt;. It has a syntax pretty similar to the &lt;code&gt;animateFloat&lt;/code&gt; from transition. With that in mind, we can use the &lt;code&gt;delay&lt;/code&gt; function from Coroutines before starting the animation. This will guarantee the correct behavior.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;BallPulseSyncIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;animatedValue&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Delaying using Coroutines&lt;/span&gt;
            &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70L&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="c1"&gt;// Remove delay property&lt;/span&gt;
                    &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;repeatMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RepeatMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Reverse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;animatedValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;animatedValue&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;animationValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;animatedValue&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;Ball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animatedValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, the animation will keep synchronized, even after some time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcukgnsidgxqzu6nuvj21.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcukgnsidgxqzu6nuvj21.gif" alt="Loading indicator with delay to start"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  TriangleSkewSpinIndicator
&lt;/h2&gt;

&lt;p&gt;Alright, let's go to the next one. This triangle indicator has two animations (rotation on the X-axis and Y-axis), but you need to wait for the previous one to execute to start the next one. So if we put that in a timeline, we will have something like this:&lt;/p&gt;

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

&lt;p&gt;The easiest way to evaluate something like this is to handle the animation as one thing with groups. Each group will be formed by the value and the next item in the list and take the same amount to evaluate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9tzqzqez975rsfejzug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9tzqzqez975rsfejzug.png" alt="Untitled (1)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rewriting the image in Kotlin, we would have something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;animateValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="n"&gt;animationSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AnimationSpec&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Create the groups zipping with next entry&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;groups&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;rememberUpdatedState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zipWithNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Start the state with the first value&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;

        &lt;span class="c1"&gt;// Start the animation from 0 to groups quantity&lt;/span&gt;
        &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;targetValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFloat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animationSpec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// Get which group is being evaluated&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;integerPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;finalValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInt&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

            &lt;span class="c1"&gt;// Get the current "position" from the group animation&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;decimalPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;integerPart&lt;/span&gt;

            &lt;span class="c1"&gt;// Calculate the progress between the initial and final value&lt;/span&gt;
            &lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;initialValue&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalValue&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;decimalPart&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this one implemented, the animation process will be pretty simple. Just create two variables to hold the X and Y rotation, and update the view using the &lt;code&gt;.graphicsLayer&lt;/code&gt; modifier.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;TriangleSkewSpinIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;infiniteRepeatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;durationMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;easing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LinearEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;xRotation&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;animateValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;180f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;180f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animationSpec&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;yRotation&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;animateValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;180f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;180f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="n"&gt;animationSpec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;animationSpec&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;Triangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;graphicsLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;rotationX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xRotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rotationY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yRotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And this is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqglti4cizw2e9rmq6qk5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqglti4cizw2e9rmq6qk5.gif" alt="Triangle indicator example"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The Jetpack Compose comes with an incredible animation API. It provides you many ways to implement all kinds of animations you may need. But, the current API is a bit different from the imperative version, and some paths will not be that obvious.&lt;/p&gt;

&lt;p&gt;To help you have a smooth transition to compose, Touchlab has started a project to help you with all these non-obvious paths.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/touchlab" rel="noopener noreferrer"&gt;
        touchlab
      &lt;/a&gt; / &lt;a href="https://github.com/touchlab/compose-animations" rel="noopener noreferrer"&gt;
        compose-animations
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Group of libraries to help you build better animations with Compose Multiplatform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Compose Animations&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Group of libraries to help you build better animations with &lt;a href="https://www.jetbrains.com/lp/compose-multiplatform/" rel="nofollow noopener noreferrer"&gt;Compose Multiplatform&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See docs at &lt;a href="https://touchlab.co/composeanimations" rel="nofollow noopener noreferrer"&gt;Multiplatform Compose Animations&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;License&lt;/h1&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Copyright 2024 Touchlab, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/touchlab/compose-animations" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;






&lt;p&gt;Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at &lt;a class="mentioned-user" href="https://dev.to/faogustavo"&gt;@faogustavo&lt;/a&gt; on &lt;a href="https://twitter.com/faogustavo" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, the &lt;a href="https://slack.kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin Slack&lt;/a&gt;, or &lt;a href="http://slack.androiddevbr.org/" rel="noopener noreferrer"&gt;AndroidDevBr Slack&lt;/a&gt;. And if you find all this interesting, maybe you'd like to &lt;a href="https://touchlab.co/contact-us/" rel="noopener noreferrer"&gt;work with&lt;/a&gt; or &lt;a href="https://touchlab.co/careers-3/" rel="noopener noreferrer"&gt;work at&lt;/a&gt; Touchlab.  &lt;/p&gt;

</description>
      <category>android</category>
      <category>compose</category>
      <category>animations</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
