<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Thomas Kioko™ on Medium]]></title>
        <description><![CDATA[Stories by Thomas Kioko™ on Medium]]></description>
        <link>https://medium.com/@_thomaskioko?source=rss-f0e2842a5e16------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*x2yG4wwtgcyYVtw9.jpeg</url>
            <title>Stories by Thomas Kioko™ on Medium</title>
            <link>https://medium.com/@_thomaskioko?source=rss-f0e2842a5e16------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:30:37 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@_thomaskioko/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Simplifying Test Infrastructure with kotlin-inject-anvil]]></title>
            <link>https://medium.com/@_thomaskioko/simplifying-test-infrastructure-with-kotlin-inject-anvil-54b820eb1b8d?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/54b820eb1b8d</guid>
            <category><![CDATA[kotlin-inject]]></category>
            <category><![CDATA[dependency-inversion]]></category>
            <category><![CDATA[kmm]]></category>
            <category><![CDATA[anvil]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Sat, 16 Aug 2025 15:33:35 GMT</pubDate>
            <atom:updated>2025-08-16T15:33:35.145Z</atom:updated>
            <content:encoded><![CDATA[<p>Testing in projects can quickly become complex, especially when managing dependencies across different platforms. In this post, I’ll share my journey of refactoring TvManiac’s test infrastructure from manually creating fake presenter factories to using proper dependency injection with <a href="https://github.com/amzn/kotlin-inject-anvil">kotlin-inject-anvil</a> to create a test component that wires the fixtures for us.</p><p>This is a blog of an ongoing series on my journey with Kotlin Multiplatform.</p><ul><li><a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95"><strong>Going Modular — The Kotlin Multiplatform Way</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-preferences-datastore-674382443262"><strong>KMM Preferences Datastore</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-environment-variables-part-1-877091c2d5b4"><strong>KMP Environment Variables (Part 1)</strong></a></li><li><a href="https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65"><strong>Intercepting Ktor Network Responses in Kotlin Multiplatform</strong></a></li><li><a href="https://medium.com/proandroiddev/navigating-the-waters-of-kotlin-multiplatform-exploring-navigation-solutions-eef81aaa1a61"><strong>Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions</strong></a></li><li><a href="https://medium.com/@_thomaskioko/enhancing-ios-development-with-swift-ui-packages-kmm-fa31901fa146"><strong>Enhancing iOS UI Previews: Swift UI Packages &amp; Kotlin Multiplatform Mobile</strong></a><strong>.</strong></li><li><a href="https://medium.com/proandroiddev/integrate-kotlin-inject-anvil-to-tv-maniac-e1330c9cb566"><strong>Integrate Kotlin-Inject-Anvil To Tv Maniac</strong></a></li><li><strong>Simplifying KMP Test Infrastructure with kotlin-inject-anvil— </strong>You are here<strong>. 👈</strong></li></ul><p>The complete implementation is available in the <a href="https://github.com/thomaskioko/tv-maniac">TvManiac</a>. Let’s jump right in.</p><h3><strong>The Problem: Manual Test Doubles</strong></h3><p>Previously, our tests looked like this. This was a big object and I had to change it every time I added a new feature/dependencies to the project.</p><pre>class DefaultRootComponentTest {<br>    @Test<br>    fun `initial state should be Home`() = runTest {<br>        val testComponent = TestComponent::class.create()<br>        val rootComponent = DefaultRootPresenter(<br>            componentContext = componentContext,<br>            rootNavigator = FakeDefaultRootNavigator(),<br>            discoverFactory = FakeDiscoverPresenterFactory(),<br>            homeFactory = FakeHomePresenterFactory(<br>                traktAuthRepository = FakeTraktAuthRepository()<br>            ),<br>            // ... more fake factories<br>        )<br><br>        // Test implementation<br>    }<br>}</pre><p>RootComponentTest required manually creating fake presenter factories, leading to:</p><ul><li>Boilerplate code duplication across tests.</li><li>Maintenance overhead when adding new dependencies.</li></ul><h3><strong>The Solution: Test Components with DI</strong></h3><p>The goal was simple: leverage <a href="https://github.com/amzn/kotlin-inject-anvil">kotlin-inject-anvil</a> to create test components that automatically provide all required dependencies, just like our production code.</p><blockquote><strong>Note:</strong> This pattern is not used across the entire project. I am using this in the rootComponent as this requires quite some dependencies and the graph is a bit complex. Other unit tests use the fakes directly and there’s no need for the test component. This solution is for complex objects. In this case, the App’s root component.</blockquote><h4><strong>Step 1: Creating the Test Scope</strong></h4><p>First, I defined a dedicated scope for test dependencies. It’s a simple interface and acts as a marker for the DI processor and does not affect the production components.</p><pre>interface TestScope</pre><h4><strong>Step 2: Test Modules for Dependencies</strong></h4><p>To provide test implementations, I created TestDataModule that adds all the required dependencies. We use fakes in the case and not the actual implementation. With the use of dependency inversion, we can easily create fake implementations.</p><pre>@ContributesTo(TestScope::class)<br>interface TestDataModule {<br>    @Provides<br>    @SingleIn(TestScope::class)<br>    fun provideDatastoreRepository(): DatastoreRepository = FakeDatastoreRepository()<br><br>    @Provides<br>    @SingleIn(TestScope::class)<br>    fun provideTraktAuthManager(): TraktAuthManager = FakeTraktAuthRepository()<br>}</pre><h4><strong>Step 3: Platform-Specific Test Components</strong></h4><p>I then created platform-specific test components instead of trying to share them in commonMain. Since we are using DI, anvil will generate the specific component for each platform. We need to extend the platform component in our test since this will have all the bindings with everything stitched together. So we need to do structure our tests to follow this structure.</p><blockquote><strong>Note</strong>: That generated type is named after our component with the Merged suffix: TestJvmComponentMerged.</blockquote><pre>@Component<br>@SingleIn(TestScope::class)<br>@MergeComponent(TestScope::class)<br>abstract class TestJvmComponent : TestJvmComponentMerged {<br>    abstract val datastoreRepository: DatastoreRepository<br>    abstract val traktAuthManager: TraktAuthManager<br>    abstract val rootPresenterFactory: DefaultRootPresenter.Factory<br>    abstract val homePresenterFactory: DefaultHomePresenter.Factory<br>}</pre><p>The iOS component follows the same pattern:</p><pre>@Component<br>@SingleIn(TestScope::class)<br>@MergeComponent(TestScope::class)<br>abstract class TestIosComponent : TestIosComponentMerged {<br>    abstract val datastoreRepository: DatastoreRepository<br>    abstract val traktAuthManager: TraktAuthManager<br>    abstract val rootPresenterFactory: DefaultRootPresenter.Factory<br>    abstract val homePresenterFactory: DefaultHomePresenter.Factory<br>}</pre><h4><strong>Step 4: Refactoring Tests</strong></h4><p>The real magic happens in the test refactoring. I transformed tests to use an abstract class pattern that separates shared test logic from platform-specific DI setup. The platform spefict test simply initialize the objects and all the tests reside in `commonMain`. Each platform then initialized it’s component.</p><ul><li>Jvm -&gt; TestJvmComponent</li><li>iOS -&gt; TestIosComponent</li></ul><p><strong>commonMain</strong></p><pre>abstract class DefaultRootComponentTest {<br>    abstract val rootPresenterFactory: DefaultRootPresenter.Factory<br>    abstract val datastoreRepository: DatastoreRepository<br><br>    @Test<br>    fun `initial state should be Home`() = runTest {<br>        val rootComponent = rootPresenterFactory.create(componentContext)<br><br>        turbineScope {<br>            rootComponent.stack.test {<br>                val config = awaitItem().active.configuration<br>                assertThat(config).isInstanceOf(Config.Home::class)<br>            }<br>        }<br>    }<br>}</pre><p>Provide Platform-specific implementations:</p><p><strong>jvmTest</strong></p><pre>internal class DefaultRootComponentJvmTest : DefaultRootComponentTest() {<br>    private val testComponent: TestJvmComponent = TestJvmComponent::class.create()<br><br>    override val rootPresenterFactory: DefaultRootPresenter.Factory<br>        get() = testComponent.rootPresenterFactory<br><br>    override val datastoreRepository: DatastoreRepository<br>        get() = testComponent.datastoreRepository<br>}</pre><p><strong>iOSTest</strong></p><pre>internal class DefaultRootComponentJvmTest : DefaultRootComponentTest() {<br>    private val testComponent: TestIosComponent = TestIosComponent.create()<br><br>    override val rootPresenterFactory: DefaultRootPresenter.Factory<br>        get() = testComponent.rootPresenterFactory<br><br>    override val datastoreRepository: DatastoreRepository<br>        get() = testComponent.datastoreRepository<br>}</pre><p>This allows us to easily scale our tests. Say we add support for desktop app, we only need to add the DesktopComponent and leverage the existing tests.</p><h3><strong>Key Learnings</strong></h3><h4><strong>1. Dependency Inversion &amp; Tests</strong></h4><p>The project uses Dependency Inversion pattern and this makes testing effective. This allows us to decouple dependencies and test them in isolation. We also use Fakes in the projects and no mocks. With the use of dependency inversion, we can avoid using mocks in the project. Below are some good articles that talk more about this.</p><ul><li><a href="https://www.billjings.net/posts/title/fakes-are-great-but-mocks-i-hate/?up=technical">Fakes Are Great, But Mocks I Hate</a></li><li><a href="https://ryanharter.com/blog/2020/06/replacing-mocks/">Replacing Mocks</a></li><li><a href="https://abseil.io/resources/swe-book/html/ch13.html">Test Doubles</a></li><li><a href="https://developer.android.com/training/testing/fundamentals/test-doubles#types">Use test doubles in Android</a></li></ul><h4><strong>2. Platform-Specific Test Components</strong></h4><p>Initially, I tried placing TestComponent in commonMain, but this doesn’t work with kotlin-inject’s code generation. Each platform needs its own component to properly generate the dependency graph.</p><h4><strong>3. Useful for Complex Objects</strong></h4><p>As much as this seems like overkill for this project, this demonstrates how this can be done in bigger projects. This also allows us to easily add new dependencies without touching the tests</p><h4><strong>What’s Next?</strong></h4><p>This test infrastructure sets the foundation for more robust testing patterns. Future improvements could include:</p><ul><li>Adding integration test components with real implementations.</li><li>Exploring shared test components for cross-platform integration tests.</li><li>Maybe splitting the RootComponent to smaller components.</li></ul><p>Until we meet again, folks. Happy coding! ✌️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54b820eb1b8d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integrate Kotlin-Inject-Anvil To Tv Maniac]]></title>
            <link>https://proandroiddev.com/integrate-kotlin-inject-anvil-to-tv-maniac-e1330c9cb566?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1330c9cb566</guid>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Sat, 07 Dec 2024 12:08:02 GMT</pubDate>
            <atom:updated>2024-12-08T04:16:36.002Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Q0gZACq7a3T4cYeD" /><figcaption>Photo by <a href="https://unsplash.com/@albertstoynov?utm_source=medium&amp;utm_medium=referral">Albert Stoynov</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Intro</h3><p>If you’ve used Anvil before, you know it takes away a lot of the boilerplate code and makes DI seamless. If Anvil is new to you, it basically allows you to contribute dagger modules and component interfaces to your DI graph, merge all the contributions, and add them to your component during compilation. Ralf Wonderatschek and Gabriel Peal gave an in-depth talk about this. <a href="https://www.droidcon.com/2022/06/28/dagger-anvil-learning-to-love-dependency-injection/">Dagger + Anvil: Learning to Love Dependency Injection.</a> You should check it out.</p><p>I have been using <a href="https://github.com/evant/kotlin-inject">kotlin-inject</a> on <a href="https://github.com/thomaskioko/tv-maniac">my pet project</a> for a while now, and I have had a good time with it, coming from using Dagger in other projects. One thing I missed was using Anvil. This was not available until recently, when <a href="https://github.com/amzn/kotlin-inject-anvil?tab=readme-ov-file">kotlin-inject-anvil</a> joined the chat.</p><p>This is a blog of an ongoing series on my journey with Kotlin Multiplatform. This article will focus on my experience and journey integrating/migrating to kotlin-inject-anvil into the project.</p><ul><li><a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95"><strong>Going Modular — The Kotlin Multiplatform Way</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-preferences-datastore-674382443262"><strong>KMM Preferences Datastore</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-environment-variables-part-1-877091c2d5b4"><strong>KMP Environment Variables (Part 1)</strong></a></li><li><a href="https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65"><strong>Intercepting Ktor Network Responses in Kotlin Multiplatform</strong></a></li><li><a href="https://medium.com/proandroiddev/navigating-the-waters-of-kotlin-multiplatform-exploring-navigation-solutions-eef81aaa1a61"><strong>Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions</strong></a></li><li><a href="https://medium.com/@_thomaskioko/enhancing-ios-development-with-swift-ui-packages-kmm-fa31901fa146"><strong>Enhancing iOS UI Previews: Swift UI Packages &amp; Kotlin Multiplatform Mobile</strong></a><strong>.</strong></li><li><strong>Integrate Kotlin-Inject-Anvil To Tv Maniac — </strong>You are her<strong>e.</strong> 👈</li></ul><p>If you want to see the code, here’s the <a href="https://github.com/thomaskioko/tv-maniac/pull/363">pull request</a>.</p><h3>Koltlin-Inject-Anvil Integration</h3><p>Before integrating <a href="https://github.com/amzn/kotlin-inject-anvil?tab=readme-ov-file">kotlin-inject-anvil</a>, one thing that bothered me was how to approach the integration/migration. I thought the process would be a pain as I already have multiple modules in my project. Do I rip the bandaid off and do it all at once? Is it possible to do it gradually? Spoiler alert: it is possible to do it gradually. This approach might not work for your project, depending on the size of the team. There are multiple ways of doing this, but this worked for me. This approach made it easier to determine if I broke the current implementation or introduced new errors.</p><p>Here’s a quick overview of how I approached the migration.</p><ul><li>Add dependencies</li><li>Apply@ContributesTo annotation</li><li>Apply @ContributesBinding annotation</li><li>Add ksp kotlin-inject-anvil compiler dependencies.</li><li>Delete component interfaces.</li><li>Replace @Component with @MergeComponent and create a subcomponent.</li></ul><p>Let’s take a quick look at how each step is implemented.</p><h3>Add kotin-inject-anvil Dependencies.</h3><p>This is pretty straightforward. We need to add the dependencies to our project.</p><pre>kotlinInject-anvil-compiler = { group = &quot;software.amazon.lastmile.kotlin.inject.anvil&quot;, name = &quot;compiler&quot;, version.ref = &quot;kotlin-inject-anvil&quot; }<br>kotlinInject-anvil-runtime = { group = &quot;software.amazon.lastmile.kotlin.inject.anvil&quot;, name = &quot;runtime&quot;, version.ref = &quot;kotlin-inject-anvil&quot; }<br>kotlinInject-anvil-runtime-optional = { group = &quot;software.amazon.lastmile.kotlin.inject.anvil&quot;, name = &quot;runtime-optional&quot;, version.ref = &quot;kotlin-inject-anvil&quot; }</pre><p>kotlinInject-anvil-runtime-optional is optional, and your project would work without it. I added it so I can get rid of my custom scope and use kotlin-inject-anvil’s scopes to keep everything consistent.</p><p>To make things easier, I created a bundle with kotlin-inject dependencies, and I use that instead.</p><pre>[bundles]<br>kotlinInject = [<br>  &quot;kotlinInject-runtime&quot;,<br>  &quot;kotlinInject-anvil-runtime&quot;,<br>  &quot;kotlinInject-anvil-runtime-optional&quot;<br>]</pre><p>We can then add it to our module like so. implementation(libs.bundles.kotlinInject)</p><h3>Add @ContributesTo Annotation</h3><p>We can now annotate our interface components with @ContributesTo. I also replaced my custom scope with kotlin-inject-anvil scope: @ApplicationScope -&gt; @SingleIn(AppScope::class). As mentioned, this is optional and will work with your custom scopes. Here’s how the component looks.</p><p><strong>Before</strong></p><pre>interface CastComponent {<br>  <br>  @Provides<br>  @ApplicationScope  <br>  fun provideCastDao(bind: DefaultCastDao): CastDao = bind<br>  <br>  @Provides<br>  @ApplicationScope<br>  fun provideCastRepository(bind: DefaultCastRepository): CastRepository = bind<br>}</pre><p><strong>After</strong></p><pre>@ContributesTo(AppScope::class)<br>interface CastComponent {<br>  <br>  @Provides<br>  @SingleIn(AppScope::class)<br>  fun provideCastDao(bind: DefaultCastDao): CastDao = bind<br>  <br>  @Provides<br>  @SingleIn(AppScope::class)<br>  fun provideCastRepository(bind: DefaultCastRepository): CastRepository = bind<br>}</pre><p>One small thing I did later was move the @SingleIn annotation to the class instead of having it in the binding functions.</p><h3>Add @ContributesBinding Annotation</h3><p>The next thing we can do is annotate all classes that have interface implementations with @ContributesBinding. Once we’ve plugged everything in, Anvil will provide the bindings for us, and we can get rid of the component above with the manual binding.</p><p><strong>Before</strong></p><pre>@Inject<br>class DefaultCastRepository(<br>  private val dao: CastDao,<br>) : CastRepository {<br>    ...<br>}</pre><p><strong>After</strong></p><pre>@Inject<br>@SingleIn(AppScope::class)<br>@ContributesBinding(AppScope::class)<br>class DefaultCastRepository(<br>  private val dao: CastDao,<br>) : CastRepository {<br>    ...<br>}</pre><h3>Add KSP Dependencies.</h3><p>To check if the changes we’ve made work as intended, we can add the Kotlin inject Anvil compiler dependency and generate the component classes. addKspDependencyForAllTargets(libs.kotlinInject.anvil.compiler). addKspDependencyForAllTargets is an extension function that creates KSP configurations for each target. e.g kspAndroid kspIosArm64</p><p>We can build our app and take a look at the generated code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/726/0*kgF_dICjfmGL6Q8C" /></figure><p>Anvil will generate the bindings for us similarly to what we had above. This will be generated for all our classes annotated with @ContributesBinding(AppScope::class).</p><pre>@Origin(value = DefaultCastRepository::class)<br>public interface ComThomaskiokoTvmaniacDataCastImplementationDefaultCastRepository {<br>  <br>  @Provides<br>  public<br>      fun provideDefaultCastRepositoryCastRepository(defaultCastRepository: DefaultCastRepository):<br>      CastRepository = defaultCastRepository<br>}</pre><h3>Delete Manual Bindings.</h3><p>Now that our bindings and components are being generated, we can delete our component interfaces with provider functions.</p><p>In my previous implementation, each module was responsible for creating its own DI component. The shared module then added all these SuperType Components to the parent/final component for each platform component. This is a bit painful and can easily get out of hand as your project grows. 😮‍💨</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/516/0*UmcoYHnJjpYp8Kp8" /></figure><p>Thanks to kotlin-inject-anvil, we can get rid of these as they are now generated for us once we add the merge annotation. 🥳</p><h3>Final Boss: @MergeComponent Annotation</h3><h3>@ContributesSubcomponent Annotation</h3><p>Since we can only have one component annotated with @MergeComponent, we need to annotate ActivityComponent to @ContributesSubcomponent, create a factory that our parent scope will implement.</p><p><strong>Before</strong></p><pre>@SingleIn(ActivityScope::class)<br>@Component<br>abstract class ActivityComponent(<br>  @get:Provides val activity: ComponentActivity,<br>  @get:Provides val componentContext: ComponentContext = activity.defaultComponentContext(),<br>  @Component<br>  val applicationComponent: ApplicationComponent =<br>    ApplicationComponent.create(activity.application),<br>) : NavigatorComponent, TraktAuthAndroidComponent {<br>  abstract val traktAuthManager: TraktAuthManager<br>  abstract val rootPresenter: RootPresenter<br>  <br>companion object<br>}</pre><p><strong>After</strong></p><p>You should note that we converted our abstract class to an interface, as only interfaces can be annotated with contributed @ContributesSubcomponent. For more details on annotation usage and behavior, <a href="https://github.com/amzn/kotlin-inject-anvil/blob/main/runtime/src/commonMain/kotlin/software/amazon/lastmile/kotlin/inject/anvil/ContributesSubcomponent.kt">see the documentation.</a></p><pre>@ContributesSubcomponent(ActivityScope::class)<br>@SingleIn(ActivityScope::class)<br>interface ActivityComponent {<br><br>  @Provides<br>  fun provideComponentContext(<br>    activity: ComponentActivity<br>  ): ComponentContext = activity.defaultComponentContext()<br><br>  val traktAuthManager: TraktAuthManager<br>  val rootPresenter: RootPresenter<br><br>  @ContributesSubcomponent.Factory(AppScope::class)<br>  interface Factory {<br>    fun createComponent(<br>      activity: ComponentActivity<br>    ): ActivityComponent<br>  }<br>}</pre><h3>@MergeComponent Annotation</h3><p>To create our graph and our components to our graph, we need to replace kotlin-injects @Component with kotlin-inject-anvil @MergeComponent and get rid of the SharedComponent.</p><p><strong>Before</strong></p><pre>@Component<br>@SingleIn(AppScope::class)<br>abstract class ApplicationComponent(<br>  @get:Provides val application: Application,<br>) : SharedComponent() {<br>  abstract val initializers: AppInitializers<br>  companion object<br>}</pre><p><strong>After</strong></p><p>I added annotation, removed the supertype from the application component, and added ActivityComponent.Factory.</p><pre>@MergeComponent(AppScope::class)<br>@SingleIn(AppScope::class)<br>abstract class ApplicationComponent(<br>  @get:Provides val application: Application,<br>) : ActivityComponent.Factory {<br>  abstract val initializers: AppInitializers<br>  abstract val activityComponentFactory: ActivityComponent.Factory<br>}</pre><p>Now, if we look at the generated code, we can see that Anvil adds all the generated components to our graph when we compile the app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KpjwBgVoFICJmiNI" /></figure><p>If you forget to delete any provide functions, you will get the following error at compile time.</p><pre>e: [ksp] Cannot provide: com.thomaskioko.tvmaniac.data.cast.api.CastDao<br>e: [ksp] as it is already provided</pre><p>This is expected; you can track down the duplicate provide method and delete it.</p><h3>Conclusion</h3><p>With this in place, we have now gotten rid of manual bindings, replacing them with @ContributesTo and @ContributesBinding. We also deleted our god component class and got rid of a lot of boilerplate, thanks to Anvil.</p><p><a href="https://x.com/vRallev">@Ralf</a> and all the contributors have done a fantastic job with <a href="https://github.com/amzn/kotlin-inject-anvil">kotlin-inject-anvil</a>. The integration was smooth. I’m looking forward to how these libraries evolve. (Maybe it should be renamed to KiAnvil. Get it? You know, like Keanu, because of how lethal it feels? No? 😂 Don’t worry, I will see myself out.)</p><p>Thanks, <a href="https://x.com/vRallev">@Ralf</a>, for reviewing the article. Until we meet again, folks. Happy coding! ✌️</p><h3>References</h3><ul><li><a href="https://www.droidcon.com/2022/06/28/dagger-anvil-learning-to-love-dependency-injection/">Dagger + Anvil: Learning to Love Dependency Injection.</a>.</li><li><a href="https://kotlinlang.org/docs/ksp-multiplatform.html">KSP with Kotlin Multiplatform</a></li><li><a href="https://github.com/amzn/kotlin-inject-anvil">Kotlin Inject Anvil README</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1330c9cb566" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/integrate-kotlin-inject-anvil-to-tv-maniac-e1330c9cb566">Integrate Kotlin-Inject-Anvil To Tv Maniac</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Enhancing iOS UI Previews: Swift UI Packages & Kotlin Multiplatform Mobile.]]></title>
            <link>https://medium.com/@_thomaskioko/enhancing-ios-development-with-swift-ui-packages-kmm-fa31901fa146?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa31901fa146</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kmm]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[swiftui-previews]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Tue, 10 Sep 2024 21:43:10 GMT</pubDate>
            <atom:updated>2024-09-22T09:21:23.691Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nTjNIEs0mpZrydPw" /><figcaption>Photo by <a href="https://unsplash.com/@akshar_dave?utm_source=medium&amp;utm_medium=referral">Akshar Dave🌻</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Intro:</h3><p>Remember our adventure in <a href="https://medium.com/better-programming/going-modular-the-kotlin-multiplatform-way-132c3dee6c95">Going Modular — The Kotlin Multiplatform</a> Way? Well, this is a continuation of that. (Sort of 😁). I say sort of because this article focuses on the Swift side of things. We will explore how creating UI components in a separate Swift package can significantly improve the development experience when working on the iOS App.</p><p>This is a blog of an ongoing series on my journey with Kotlin Multiplatform.</p><ul><li><a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95"><strong>Going Modular — The Kotlin Multiplatform Way</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-preferences-datastore-674382443262"><strong>KMM Preferences Datastore</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-environment-variables-part-1-877091c2d5b4"><strong>KMP Environment Variables (Part 1)</strong></a></li><li><a href="https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65"><strong>Intercepting Ktor Network Responses in Kotlin Multiplatform</strong></a></li><li><a href="https://medium.com/proandroiddev/navigating-the-waters-of-kotlin-multiplatform-exploring-navigation-solutions-eef81aaa1a61"><strong>Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions</strong></a></li><li><strong>Enhancing iOS UI Previews: Swift UI Packages &amp; Kotlin Multiplatform Mobile. — </strong>You are her<strong>e.</strong> 👈</li></ul><p>If you are interested in the code, <a href="https://github.com/thomaskioko/tv-maniac/pull/286">here is the Pull Request</a>. Let’s get this party started.</p><h3>The KMM Preview Headache 🤕</h3><p>While working with KMM on iOS, one of my biggest headaches is that Previews don’t load in XCode. I don’t know why XCode does not play nice with KMM. It either takes too long to load or fails. The only way to see what I am creating is by running the application. This is frustrating as it slows development time.</p><h3>Enter Separate Swift Packages 📦</h3><p>By relocating our UI components into a separate Swift package, we’re not just organizing our code, but we’re also unlocking a host of benefits that can significantly enhance our development experience:</p><ol><li><strong>Functional SwiftUI Previews</strong></li></ol><ul><li>By isolating views from the KMM framework, providing real-time UI development feedback, and improving the developer experience, we can load views faster.</li></ul><p><strong>2. Improved Reusability</strong></p><ul><li>Components can be easily reused across the application, leading to a more modular architecture and reducing code duplication.</li></ul><p><strong>3. Streamlined Testing</strong></p><ul><li>With this in place, Snapshot testing becomes a viable option for ensuring UI consistency. Coming soon. 🤓</li></ul><p><strong>4. Optimized Project Structure</strong></p><ul><li>A clear structure makes it easy to maintain and update our components.</li></ul><h3>App structure:</h3><p>I ended up creating two packages:</p><ul><li><strong>SwiftUIComponents:</strong> This package contains common views used in various screens. Colors, Buttons, CardView, PosterImage etc</li><li><strong>TVManiacUI:</strong> This Package includes Screen UI Views. eg. CastList, TrailerList, ShowInfoView. etc</li></ul><p>Below is what the structure of the App looks like.</p><pre>TvManiac/<br>├── shared/<br>│   └── ... (Kotlin shared code)<br>├── androidApp/<br>│   └── ... (Android-specific code)<br>├── iosApp/<br>│   ├── iosApp.xcodeproj<br>│   ├── iosApp/<br>│   │   └── ... (iOS app-specific code)<br>│   └── Modules/<br>│       └── SwiftUIComponents/<br>│       │   ├── Package.swift<br>│       │   └── Sources/<br>│       │       └── SwiftUIComponents/<br>│       │           └──Components/<br>│       │                 ├── BottomNavigation.swift<br>│       │                 ├── HeaderContentView.swift<br>│       │                 └── ...<br>│       │           ├── CollapsibleView.swift<br>│       │           ├── TrailerItemView.swift<br>│       │           └── ...<br>│       └── TVManiacUI/<br>│           ├── Package.swift<br>│           └── Sources/<br>│               └── TVManiacUI/<br>│                   └──Models/<br>│                       └── SwiftCast.swift<br>│                       └── SwiftTrailer.swift<br>│                   ├── CastListView.swift<br>│                   ├── ShowInfoView.swift<br>│                   ├── ProviderListView.swift<br>│                   └── ...</pre><p><strong>Implementing the Approach</strong></p><p>Integrating a package in XCode is a straightforward process. These are the steps I followed:</p><p>1. <strong>Package Creation and Configuration</strong></p><ul><li>Create a new Swift package (`SwiftUIComponents`) using your preferred method (Xcode UI or Swift Package Manager CLI).</li><li>Configure the `Package.swift` file to specify the iOS deployment target and any necessary dependencies.</li></ul><p>2. <strong>Component Migration</strong></p><ul><li>Migrate SwiftUI views and components from your KMP iOS project to the new package.</li><li>Refactor as needed to ensure components are self-contained and don’t rely on KMM-specific code.</li></ul><p>3. <strong>Dependency Management</strong></p><ul><li>If your UI components require a specific package, use Swift Package Manager’s dependency declaration to manage package relationships. In my case, I added `SDWebImageSwiftUI` and `YouTubePlayerKit` and removed them from the main app.</li></ul><p>4. <strong>Final Step</strong></p><ul><li>In your main iOS project, go to File &gt; Add Packages.</li><li>Select `SwiftUIComponents` package.</li><li><strong>(Optional)</strong> Create a new scheme and select the framework, in this case, `SwiftUIComponents.` This allows us to switch between the Main app and the UI package.</li></ul><p>With that in place, here’s an example of how the project structure looks after implementing this approach. 🥳</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eOKfVygXHrPo_vchGJyr4w.png" /><figcaption>UI Components Preview</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*73RKkon170PtrKEJD_ZZzQ.png" /><figcaption>TvManiacUI Preview</figcaption></figure><p><strong>Conclusion</strong></p><p>I highly recommend trying this approach. It’s made my life easier, my code cleaner, and the general structure better. Next, we might add snapshot tests, a CI/CD job, and more cleanup.</p><p>Until we meet again, folks. Happy coding! ✌️</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa31901fa146" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions]]></title>
            <link>https://proandroiddev.com/navigating-the-waters-of-kotlin-multiplatform-exploring-navigation-solutions-eef81aaa1a61?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/eef81aaa1a61</guid>
            <category><![CDATA[compose]]></category>
            <category><![CDATA[decompose]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Fri, 05 Apr 2024 15:13:28 GMT</pubDate>
            <atom:updated>2024-04-05T15:57:31.068Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PsKLjNLEUUbRivtv" /><figcaption>Photo by <a href="https://unsplash.com/@alex_andrews?utm_source=medium&amp;utm_medium=referral">Alexander Andrews</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><blockquote><strong>Disclaimer</strong>: This blog post does not aim to compare various navigation libraries; instead, it offers insights into my personal journey of arriving at a navigation solution. The intention is not to provide a comparative analysis of different libraries but to share my decision-making process.</blockquote><h3><strong>Intro:</strong></h3><p>Now, let’s delve into the journey. My initial focus was on streamlining Compose Screens and refining the presentation layer. I wanted to relocate the navigation logic from the screens to the presentation layer. This decision stemmed from my perspective that navigation is inherently more of a state management concern than a UI element. After conducting some brief research, I decided to centralize and share the navigation logic. Have the logic in a separate navigation module and have it handle routing to different screens. After some experimentation, I shared the navigation logic between iOS and Android.</p><p>This is a part of an ongoing series on my journey with Kotlin Multiplatform.</p><ul><li><a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95"><strong>Going Modular — The Kotlin Multiplatform Way</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-preferences-datastore-674382443262"><strong>KMM Preferences Datastore</strong></a></li><li><a href="https://medium.com/@_thomaskioko/kmm-environment-variables-part-1-877091c2d5b4"><strong>KMP Environment Variables (Part 1)</strong></a></li><li><a href="https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65"><strong>Intercepting Ktor Network Responses in Kotlin Multiplatform</strong></a></li><li><strong>Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions</strong> — You are here. 👈</li></ul><p>If you’d like to know how things looked initially, read <a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95"><strong>Going Modular — The Kotlin Multiplatform Way</strong></a>. Let’s dive in and see how we ended up here.</p><h3><strong>Navigating the Sea Of Libraries</strong></h3><p>There are a couple of things to consider before deciding what library to use, as there’s no “best” library. This could be something like how new the library is. What does the support look like? Is it actively maintained? Might it add complexity to your app? If you are in a team, how many people are familiar with it? How does it impact your architecture? I could go on, but it depends on your desire.</p><p>There are a couple of third-party alternatives that you can choose from:</p><ul><li><a href="https://voyager.adriel.cafe/">Voyager</a>: A pragmatic approach to navigation</li><li><a href="https://arkivanov.github.io/Decompose/">Decompose</a>: An advanced approach to navigation that covers the full lifecycle and any potential dependency injection.</li><li><a href="https://bumble-tech.github.io/appyx/">Appyx</a>: Model-driven navigation with gesture control.</li><li><a href="https://tlaster.github.io/PreCompose/">PreCompose</a>: A navigation and view model inspired by Jetpack Lifecycle, ViewModel, LiveData, and Navigation</li></ul><p>Fortunately or unfortunately, I only tried two of the mentioned libraries; my experience is below.</p><h4><strong>1. Voyager</strong></h4><p><a href="https://voyager.adriel.cafe/">Voyager</a> is a new navigation library explicitly built for Compose multiplatform, which is designed to make navigation in Compose-based apps more straightforward and intuitive.</p><p><strong>Setup &amp; General Use</strong></p><p>My experience with Voyager was seamless. It’s easy to set up and get things going. The `ScreenModel` class is also useful for multiplatform state management.</p><p><strong>*Dependency injection(Optional)</strong></p><p>Voyager integrates well with the different dependency injection frameworks. It currently supports <a href="https://insert-koin.io/">koin</a>, <a href="https://github.com/kosi-libs/Kodein">kodein</a>, hilt, and <a href="https://github.com/evant/kotlin-inject">Kotlin-inject</a>.</p><p><strong>Conclusion</strong></p><p>I did not go with Voyager because I wanted to keep the UI side native per platform (Android: Compose &amp; iOS SwiftUI) and only share the ScreenModel. It’s possible to create a custom ScreenModel/ViewModel where the Lifecycle is bound on the Voyager lifecycle and, on iOS, the UiViewController lifecycle. However, I was looking for something that works out of the box. This was an oversight on my end, but it was a good experiment. If/when I create a Compose Multiplatform App, I will go with Voyager.</p><h4><strong>2. Decompose</strong></h4><p>Decompose is a Kotlin Multiplatform library for breaking down your code into tree-structured lifecycle-aware business logic components (aka BLoC), with routing functionality and pluggable UI</p><p><strong>Setup &amp; General Use</strong></p><p>Decompose does a good job of decoupling from the OS libraries. I had a hard time getting things up and running compared to my experience with Voyager. Once you understand the core concepts, it’s not that bad. The good thing is the project is well documented, and there are multiple project samples. [John O’Reilly’s](<a href="https://johnoreilly.dev/">https://johnoreilly.dev/</a>) project, Confetti, also helped immensely with my migration.</p><p><strong>*Dependency injection(Optional)</strong></p><p>Decompose is flexible and should work with any dependency injection framework. It currently supports <a href="https://insert-koin.io/">koin</a>, <a href="https://github.com/kosi-libs/Kodein">kodein</a>, and <a href="https://github.com/evant/kotlin-inject">Kotlin-inject</a>, which I use in <a href="https://github.com/thomaskioko/tv-maniac">my project</a>].</p><p><strong>Conclusion</strong></p><p><a href="https://github.com/arkivanov/Decompose">Decompose</a> works for me because it allows me to keep the native UI instead of using Compose Multiplatform and share the navigation and presentation logic between platforms.</p><h3><strong>Pre-Decompose</strong></h3><p>Let’s take a trip down memory lane and see what things look like before moving to decompose. Each platform handled its navigation — i.e., Jetpack Navigation on Android and Navigation API for iOS. The image below shows what the Android architecture looked like. This was highly inspired by <a href="https://medium.com/@LachlanMcKee">Lachlan McKee’s</a> article <a href="https://medium.com/bumble-tech/scalable-jetpack-compose-navigation-9c0659f7c912">Scalable Jetpack Compose Navigation</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K1Dh9WSv-0FhnHH7gkEddQ.png" /><figcaption>Android Architecture</figcaption></figure><p>Each feature module is responsible for creating its navigation factory, which the App module adds to the NavGraph.</p><h3><strong>Hello Decompose</strong></h3><p>Let’s quickly look at how things look with decompose in place. I will not go through the setup process. There are multiple articles on how to do this, but here’s an overview of what the architecture looks like with Decompose.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ARlBPn4WWOXkb0qFo8ILzg.png" /></figure><p>Thanks to the implementation of decompose, navigation logic joins the shared codebase (KMM). This means each platform only has UI implementation, such as Android with Jetpack Compose and iOS with Swift, and no business logic.</p><p>One thing I might do in the future is move the Android Feature modules to the presentation layer, as they are currently just composables. I’m still thinking about this. 🤔</p><p>With this structure, I can easily change the presentation &amp; navigation layers and try something else if needed.</p><p>The gif below shows how both apps work with shared logic. Thanks to Decompose, we can handle navigation, display, and hide views, in this case, the BottomBar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*qbQz5TVSdP8tGvdvp1He1w.gif" /><figcaption>Android+iOS Navigation Demo</figcaption></figure><h3><strong>Until next time</strong></h3><p>There’s no silver bullet for navigation yet, so use what works for you or your team. One significant takeaway from this experiment is the importance of considering an iOS perspective first. The adage ‘If it works on iOS, it should translate smoothly to Android’ underscores the value of prioritizing platform considerations in development decisions.</p><p>If you are curious about how the Migration looked, you can look at the PR below.</p><p><a href="https://github.com/thomaskioko/tv-maniac/pull/108">Migrate to Decompose by thomaskioko · Pull Request #108 · thomaskioko/tv-maniac</a></p><p>Thank you for taking the time to join me on this journey. Feel free to share any opinions or ask any questions you may have. With that, my app has been Decomposed. ✌️</p><p>Big thanks to <a href="https://medium.com/u/ca9741bc2980">Arkadii Ivanov</a> and <a href="https://medium.com/u/cb9d86a42a08">Hannah Olukoye</a> for reviewing this post.</p><h3><strong>Resources</strong></h3><ul><li><a href="https://arkivanov.github.io/Decompose/">Decompose Documentation</a>.</li><li><a href="https://www.youtube.com/watch?v=g4XSWQ7QT8g">Shared Navigation on Kotlin Multiplatform with Decompose (KMP)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eef81aaa1a61" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/navigating-the-waters-of-kotlin-multiplatform-exploring-navigation-solutions-eef81aaa1a61">Navigating the Waters of Kotlin Multiplatform: Exploring Navigation Solutions</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Intercepting Ktor Network Responses in Kotlin Multiplatform]]></title>
            <link>https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/32946b7d4d65</guid>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[ktor]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[compose]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Tue, 25 Jul 2023 14:19:04 GMT</pubDate>
            <atom:updated>2023-07-28T15:25:01.311Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2d0w3iXK1ATLsCtQXFSOOQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/de/@designedbyflores?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Omar Flores</a> on <a href="https://unsplash.com/photos/MOO6k3RaiwE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4><strong>Introduction:</strong></h4><p>Handling various HTTP response codes is crucial when building mobile applications that communicate with APIs. This article will explore how to use <a href="https://ktor.io/docs/response-validation.html">Ktor’s `HttpResponseValidator`</a> to intercept and handle responses in mobile applications.</p><p>We’ll use <a href="https://trakt.tv/">Trakt</a> as a use case since it’s what I am for my project, but the concept should work for any API. One of the <a href="https://trakt.docs.apiary.io/#introduction/status-codes">status codes</a> from Trakt is `403` (Forbidden), which indicates that the server understands the request but refuses to authorize it. We’ll see how we can intercept such responses, format the message and present this to the user.</p><p>Below is what we’ll be able to achieve at the end of this. 😎</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kuq97P-RTzBUJS17yrRuXQ.png" /></figure><h4>TL;DR</h4><p><em>If you just want to look at the code, click the link below.</em></p><p><a href="https://github.com/c0de-wizard/tv-maniac/pull/95">Ktor Error Handling by c0de-wizard · Pull Request #95 · c0de-wizard/tv-maniac</a></p><h4>Understanding HttpResponseValidator</h4><p><a href="https://ktor.io/">Ktor</a> is a robust Kotlin-based framework for building server-side and client-side applications. It provides a flexible and extensible architecture to efficiently handle HTTP requests and responses. One of the key components in Ktor is the `HttpResponseValidator,` which allows developers to define a custom response.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1a55d37c102d8c16ac2ba6a84767d69e/href">https://medium.com/media/1a55d37c102d8c16ac2ba6a84767d69e/href</a></iframe><h4>Intercepting Responses</h4><p>To intercept responses, we need to add `HttpResponseValidator` inside the HttpClient’s body Ktor’s to check the response’s status code and take appropriate action. Before doing that, we need to enable default validation by setting the `expectSuccess` property to `true.` This terminates `HttpClient.receivePipeline` if the status code is unsuccessful.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c3fa2fa09769edfd87fe2bfc95ffe221/href">https://medium.com/media/c3fa2fa09769edfd87fe2bfc95ffe221/href</a></iframe><p>Here’s our custom exception class.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d96eeb6da96edfd1dd7ebc6d6273333/href">https://medium.com/media/6d96eeb6da96edfd1dd7ebc6d6273333/href</a></iframe><h4>API Response Wrapper</h4><p>With the HttpResponseValidator in place, we can create an extension function to wrap API responses.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/502f33d697b9c1a28a1922400e8fe112/href">https://medium.com/media/502f33d697b9c1a28a1922400e8fe112/href</a></iframe><p>You can also use <a href="https://github.com/michaelbull/kotlin-result">kotlin-result</a> or <a href="https://apidocs.arrow-kt.io/arrow-core/arrow.core/-either/index.html">Arrow’s Either</a> depending on your needs. In this case, we will create our class. `ApiResponse`</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a8af559cce6fb36741424a1464c4a81/href">https://medium.com/media/3a8af559cce6fb36741424a1464c4a81/href</a></iframe><h4>API Requests</h4><p>We can now create a function with a return type `ApiResonse&lt;T&gt;` and use the extension function we created to make the API call.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bf8e92fa8b367ca7b41ae03f7d3b9b75/href">https://medium.com/media/bf8e92fa8b367ca7b41ae03f7d3b9b75/href</a></iframe><p>You can now invoke the Api call and handle the response based on your implementation. In my case, I am using <a href="https://mobilenativefoundation.github.io/Store/">Store5</a>. (Blog coming soon). We make the API call inside the `fetcher.` If successful, cache it else, throw an exception.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/50ce979c30d1ffd483fc4d07cfc9faa0/href">https://medium.com/media/50ce979c30d1ffd483fc4d07cfc9faa0/href</a></iframe><p>By doing this, Store will wrap the exception in the `StoreReadResponse.Error` type, ensuring the flow does not break the stream and will still receive updates when data changes. We can now propagate the result and handle it in the presentation layer. In this case, when we get an exception, we show a SnackBar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kuq97P-RTzBUJS17yrRuXQ.png" /><figcaption>Exception Snackbar</figcaption></figure><p>And that’s it! 🎊 I hope this post helped. If you liked it, give it some claps! 👏</p><h4>Post Series</h4><ol><li><a href="https://betterprogramming.pub/going-modular-the-kotlin-multiplatform-way-132c3dee6c95">Going Modular — The Kotlin Multiplatform Way</a></li><li><a href="https://medium.com/@code_wizard/kmm-preferences-datastore-674382443262">KMP Preferences Datastore</a></li><li><a href="https://medium.com/@code_wizard/kmm-environment-variables-part-1-877091c2d5b4">KMP Environment Variables (Part 1)</a></li><li><a href="https://medium.com/@_thomaskioko/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65">Intercepting Ktor Network Responses in Kotlin Multiplatform.</a></li></ol><h4>Reference material</h4><ul><li><a href="https://betterprogramming.pub/swiftui-create-a-fancy-toast-component-in-10-minutes-e6bae6021984">Create a Fancy Toast Component Using SwiftUI</a></li><li><a href="https://ktor.io/docs/response-validation.html">Response validation</a></li></ul><p><em>Thanks to </em><a href="https://hannaholukoye.medium.com/"><em>Hannah Olukoye</em></a> <em>for reviewing this article.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32946b7d4d65" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65">Intercepting Ktor Network Responses in Kotlin Multiplatform</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[KMM Environment Variables Part 1]]></title>
            <link>https://medium.com/@_thomaskioko/kmm-environment-variables-part-1-877091c2d5b4?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/877091c2d5b4</guid>
            <category><![CDATA[buildconfig]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[kmp]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Sat, 13 May 2023 18:22:33 GMT</pubDate>
            <atom:updated>2023-07-20T21:13:13.480Z</atom:updated>
            <content:encoded><![CDATA[<h3>KMP Environment Variables (Part 1)</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kcQOf_w_cavHCrJi.jpeg" /><figcaption><em>Photo by </em><a href="https://unsplash.com/@scottwebb?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"><em>Scott Webb</em></a><em> on </em><a href="https://unsplash.com/photos/lnRPKo7Lo5Q?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"><em>Unsplash</em></a></figcaption></figure><p>In this article, I will talk about my experience working with environment variables on Kmm (Kotlin Multiplatform) and how I am currently using it in my project <a href="https://github.com/c0de-wizard/tv-maniac">Tv-Maniac</a></p><p><a href="https://github.com/c0de-wizard/tv-maniac">GitHub - c0de-wizard/tv-maniac: Tv-Maniac is a personalized entertainment tracking and recommendation Multiplatform app (Android &amp; iOS) for tracking TV Shows using Trakt.</a></p><h4>Environment Variables</h4><p>During the development lifecycle of a mobile app, you may create apps that use API keys or passwords. It is best practice to store such sensitive info in a secure place. In Android, you’d ideally use local.properties or gradle.properties to set this up on iOS, configuration files commonly known as xxconfig The good thing about this is you can configure it to support multiple builds/targets: e.g., Dev, QA and Prod depending on your needs.</p><p>Now that we have that in mind, how do we go about this for a KMM project? There are multiple ways you can do this, but I will only talk about the first two since it’s what I have worked with.</p><ol><li>Custom gradle plugin</li><li><a href="https://github.com/yshrsmz/BuildKonfig">BuildKonfig</a></li><li>Config file reader</li><li><a href="https://github.com/gmazzo/gradle-buildconfig-plugin">gradle-buildconfig-plugin</a></li></ol><h4>1. Custom Gradle Task</h4><p>I will not go in-depth on this but give an overview. We can use some Gradle utils to generate code from build properties. There are three main concepts that we will need to make this possible.</p><ol><li>Creating a gradle task.</li><li>Defining the generated file.</li><li>Setting the source set</li></ol><p>With that in mind, we will need to create a task that produces a file as output. We can use <a href="https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Sync.html">Sync</a> for this. According to Gradle’s documentation, it synchronizes the contents of a destination directory with some source directories and files. It’s more like a copy task.</p><p>Once we have that in place, we can dynamically use TestResourceFactory to create files. If you have a custom source set, you will need to define it. Otherwise, gradle won’t recognize it.</p><p>You can check out <a href="https://stackoverflow.com/a/74771876">this answer</a> by @aSemy on StackOverflow.</p><h4>2. BuildKonfig</h4><p>This is a library that embeds values from a gradle file. If you are an Android dev, this will feel more like home. It’s pretty simple.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*969QsUFunorGEof1.png" /></figure><p>I created a small function to help us read from gradle.properties.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FglIqFX-SCG1m11U.png" /></figure><p>Below is how we use the custom function and buildConfig to read from gradle.properties</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fTqxsGZPt-9W9CZD.png" /></figure><p>To generate the files, run ./gradlew generateBuildKonfig. The generated class will look like so:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/752/0*0OxNAffIfXEC98EU.png" /></figure><h4>3. Config File Reader</h4><p>We can create a resource reader to help read from yaml configuration files. The good thing about this is we can easily create multiple environment files for different flavors. Builkonfig supports this, but I have difficulty switching between environments in a different project.</p><p>This implementation is from <a href="https://github.com/touchlab/DroidconKotlin">Touchlab’s Droidcon KMM App</a>, which has a neat way of reading files from both iOS and Android. I’d say this is the meat of it. YamlResourceReader helps us serialize the yaml file into a data class object. readAndDecodeResource takes in two params:</p><ol><li>name: Name of the yaml file. We have this as a parameter so that you can pass in different files based on the build target you want.</li><li>strategy: Serialization strategy. We will be using kotlinx.serialization , but you could also use Gson or Jackson.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U_Cnu2DNh_UHm0JR.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HAUVBhL3KAmhAqYR.png" /></figure><h4>Android Platform Implementation</h4><p>We could also use Android AssetManager to read from the bundled assets context.assets.open(name) but this example we are using javaClass.classLoader</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0BMqmnOLBFwUaXyp.png" /></figure><h4>iOS Platform Implementation</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2JqE2Yh8loDEeAfo.png" /></figure><p>Resources Folder</p><p>We then need to add our configuration files. Kmm libraries have a structure folder for resources. You can name them whatever you want. qa.yaml or release.yaml depending on your needs. In this case, qa.yaml.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/522/0*lIt5FXXky-LbbhEa.png" /></figure><p>We need to drag or add the file on iOS, so it’s accessible. For uniformity, I have created a directory called Resources and added the file there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/233/0*1AsI1TMolqzwpePs.png" /></figure><p>One extra step was to create a symlink in the root directory so we could easily access it. (This is optional)</p><h4>Reader Implementation</h4><p>Tying things up together. We can now use the resource reader and pass the intended file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iOZNGMwFTi5CLyD-.png" /></figure><h4>Conclusion</h4><p>There are two things we need to do:</p><ol><li>Remember to add the files on iOS.</li><li>Depending on your application, you might want to ignore the file so you don’t publish sensitive info.</li></ol><p>With that, all our resources are in one directory. There might be a better way, but this has worked for me. I’m always up to learning from you, so feel free to poke holes in my implementation. 😅</p><p>In part 2, we will look at how to create different build flavors. Until then, happy coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=877091c2d5b4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[KMM Preferences Datastore]]></title>
            <link>https://medium.com/@_thomaskioko/kmm-preferences-datastore-674382443262?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/674382443262</guid>
            <category><![CDATA[data-stores]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Thu, 15 Dec 2022 09:12:32 GMT</pubDate>
            <atom:updated>2022-12-15T13:13:10.321Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WojUqOHDTdfzpoJX.png" /></figure><p>In this article, well, take a look at how to DataStore and how to use it in a Kotlin Multiplatform project.</p><p>Recently Google <a href="https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html">announced</a> a couple of Jetpack Multiplatform libraries. One is DataStore, a storage solution that allows you to store key-value pairs. This has been rewritten &amp; is now available on Kotlin Multiplatform, targeting Android &amp; iOS.</p><blockquote>Something to take note of is that <a href="https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html">these libraries</a> are experimental and should not be used on production apps!</blockquote><h4>What you will learn:</h4><ol><li>Setting up Datastore.</li><li>Dependency Injection.</li><li>Creating a repository</li><li>I am writing a test.</li></ol><h4>TvManiac Project</h4><p>A lot is changing at the moment on this project as I am cleaning up and creating a better structure for this project. The changes referenced here can be found in <a href="https://github.com/c0de-wizard/tv-maniac/pull/48/commits/5e0dec9ed729bc1c6da23196c92475b8984edf76">this commit</a>.</p><p><a href="https://github.com/c0de-wizard/tv-maniac">GitHub - c0de-wizard/tv-maniac: Tv-Maniac is a personalized entertainment tracking and recommendation Multiplatform app (Android &amp; iOS) for tracking TV Shows using Trakt.</a></p><p>Let’s get started!</p><p>For our project, we need to add the preference core dependency.</p><pre>dependencies {<br>    implementation(&quot;androidx.datastore:datastore-preferences-core:1.1.0-dev01&quot;)<br>}</pre><p>Now we need to create a function in commonMainto help us create an instance of our DataStore object. We can use expect/actual pattern and have each platform have its implementation, but I will create a function and set it up when setting up injection. It&#39;s a straightforward function that takes in two parameters: Coroutine scope and the filePath.</p><pre>fun createDataStore(<br>    coroutineScope: CoroutineScope,<br>    producePath: () -&gt; String<br>): DataStore&lt;Preferences&gt; = PreferenceDataStoreFactory.createWithPath(<br>    corruptionHandler = null,<br>    migrations = emptyList(),<br>    scope = coroutineScope,<br>    produceFile = { producePath().toPath() },<br>)<br><br>internal const val dataStoreFileName = &quot;tvmainac.preferences_pb&quot;</pre><p>And that’s it. 🙂 We can now move over to the injection of the DataStore. As I mentioned in my previous article, we have a “hybrid injection setup” where we use Hilt on the Android side and Koin on iOS; however, I plan on migrating to Koin in the future. 😅</p><h4>Dependency Injection:</h4><h4>Android:</h4><p>This should be self-explanatory:</p><pre>@Provides<br>@Singleton<br>fun provideDataStore(<br>    @ApplicationContext context: Context,<br>    @DefaultCoroutineScope defaultScope: CoroutineScope<br>): DataStore&lt;Preferences&gt; = createDataStore(<br>    coroutineScope = defaultScope,<br>    producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }<br>)</pre><h4>IOS Koin Module:</h4><p>For iOS, it’s almost similar. We need to create a function that creates the dataStoretore object and then add that to koin module</p><pre>fun dataStore(scope: CoroutineScope): DataStore&lt;Preferences&gt; = createDataStore(<br>    coroutineScope = scope,<br>producePath = {<br>    val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(<br>        directory = NSDocumentDirectory,<br>        inDomain = NSUserDomainMask,<br>        appropriateForURL = null,<br>        create = false,<br>        error = null,<br>    )<br>    requireNotNull(documentDirectory).path + &quot;/$dataStoreFileName&quot;<br>  }<br>)<br><br>actual fun settingsModule(): Module = module {<br>    single { dataStore(get()) }<br>}</pre><p>Boom. We can now create a repository and use it to get the theme and set the theme. The implementation looks like so.</p><pre>class SettingsRepositoryImpl(<br>    private val dataStore: DataStore&lt;Preferences&gt;,<br>    private val coroutineScope: CoroutineScope<br>) : SettingsRepository {<br><br>override fun saveTheme(theme: String) {<br>        coroutineScope.launch {<br>            dataStore.edit { settings -&gt;<br>                settings[KEY_THEME] = theme<br>            }<br>        }<br>    }<br><br>override fun observeTheme(): Flow&lt;Theme&gt; = dataStore.data.map { theme -&gt;<br>        when (theme[KEY_THEME]) {<br>            &quot;light&quot; -&gt; Theme.LIGHT<br>            &quot;dark&quot; -&gt; Theme.DARK<br>            else -&gt; Theme.SYSTEM<br>        }<br>    }<br><br>companion object {<br>        val KEY_THEME = stringPreferencesKey(&quot;app_theme&quot;)<br>    }</pre><h4>Testing</h4><p>The test is pretty simple.</p><h4>Creating the DataStore &amp; repository objects.</h4><pre>private var preferencesScope: CoroutineScope = CoroutineScope(testCoroutineDispatcher + Job())   <br>private val dataStore: DataStore&lt;Preferences&gt; = PreferenceDataStoreFactory.createWithPath(<br>        corruptionHandler = null,<br>        migrations = emptyList(),<br>        scope = preferencesScope,<br>        produceFile = { &quot;test.preferences_pb&quot;.toPath() },<br>    )<br>private val repository = SettingsRepositoryImpl(dataStore, testCoroutineScope)</pre><h4>Clear/remove the key after running a test.</h4><p>The other thing we need to do is clear the saved item after running a test. We could do this two ways, delete the file in the application dir or remove the preferences using the key. We will use the latter. We do that by creating a function and using @AfterTest annotation. We also need to cancel the context associated with the data store.</p><pre>@AfterTest<br>fun clearDataStore() = runBlockingTest {<br>    dataStore.edit {<br>        it.remove(KEY_THEME)<br>    }<br> preferencesScope.cancel()<br>}</pre><p>Since DataStore uses coroutines, we will use <a href="https://github.com/cashapp/turbine">Turbine</a> for tests. This isn’t complicated. Here, we test that the theme gets updated.</p><pre>@Test<br>fun when_theme_is_changed_correct_value_is_set() = runBlockingTest {<br>    repository.observeTheme().test {<br>        repository.saveTheme(&quot;dark&quot;)<br>        awaitItem() shouldBe Theme.SYSTEM //Default theme<br>        awaitItem() shouldBe Theme.DARK<br>    }<br>}</pre><p>One thing that I will improve in the future is deleting the generated test file. Right now, we ignore all .preferences_pb files created during testing.</p><p>This is how our test class looks like after stitching everything together.</p><pre>private var preferencesScope: CoroutineScope = CoroutineScope(testCoroutineDispatcher + Job())<br>private val dataStore: DataStore&lt;Preferences&gt; = PreferenceDataStoreFactory.createWithPath(<br>    corruptionHandler = null,<br>    migrations = emptyList(),<br>    scope = preferencesScope,<br>    produceFile = { &quot;test.preferences_pb&quot;.toPath() },<br>)<br>private val repository = SettingsRepositoryImpl(dataStore, testCoroutineScope)<br><br>@AfterTest<br>fun clearDataStore() = runBlockingTest {<br>    dataStore.edit {<br>        it.remove(KEY_THEME)<br>    }<br>    preferencesScope.cancel()<br>}<br><br>@Test<br>fun default_theme_is_emitted() = runBlockingTest {<br>    repository.observeTheme().test {<br>        awaitItem() shouldBe Theme.SYSTEM<br>    }<br> }<br><br> @Test<br> fun when_theme_is_changed_correct_value_is_set() = runBlockingTest {<br>    repository.observeTheme().test {<br>        repository.saveTheme(Theme.DARK)<br>        awaitItem() shouldBe Theme.SYSTEM //Default theme<br>        awaitItem() shouldBe Theme.DARK<br>        }<br>    }<br>}</pre><h4>Final Thoughts</h4><p>We’ve covered steps for using DataStore in a Kotlin Multiplatform project. I must say, it was super simple to get this up and running. You can use this in your ViewModel. For my project, I’m using a <a href="https://github.com/freeletics/FlowRedux">FlowRedux StateMachine</a>, which I will discuss in the next post.</p><p>Any comments, good or bad, are always welcome.</p><h4>Resources</h4><ul><li>Check out the <a href="https://github.com/android/kotlin-multiplatform-samples/tree/main/DiceRoller">DiceRoller</a> sample app.</li><li><a href="https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html">Jetpack Multiplatform Libraries</a>.</li></ul><p><em>Thanks to </em><a href="https://medium.com/u/ec2087b3c81f"><em>Marton Braun</em></a><em> for reviewing this article.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=674382443262" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Going Modular — The Kotlin Multiplatform Way]]></title>
            <link>https://medium.com/better-programming/going-modular-the-kotlin-multiplatform-way-132c3dee6c95?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/132c3dee6c95</guid>
            <category><![CDATA[kmp]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[multi-module-project]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Wed, 19 Jan 2022 11:32:53 GMT</pubDate>
            <atom:updated>2023-07-28T09:17:51.304Z</atom:updated>
            <content:encoded><![CDATA[<h4>Setup modular codebases</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NExDJVvg0K4-Pifg.jpeg" /></figure><p>I recently decided to dive into the Kotlin Multiplatform world. So far, so good. This article talks about my journey modularising a project I’ve been working on.</p><p>There are other ways to do this, but this worked for me.</p><h3>TvManiac</h3><p>I’m using <a href="https://developers.themoviedb.org/3">TMDB</a> to fetch Tv Show information. Here’s how it looks on both platforms. I have much work done on Android since that’s my territory. I’m updating the iOS side of things as I learn. You can find the source code for the project on <a href="https://github.com/c0de-wizard/tv-maniac">Github</a>.</p><p><a href="https://github.com/c0de-wizard/tv-maniac">GitHub - c0de-wizard/tv-maniac: Tv-Maniac is a personalized entertainment tracking and recommendation Multiplatform app (Android &amp; iOS) for tracking TV Shows using Trakt.</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oP1Yfe_hxENFrbdC5Zxtvw.png" /></figure><h3>The Shared Module</h3><p>Before discussing modularisation, let me share why I decided to do this. The shared module contains common logic for both platforms. (iOS &amp; Android).</p><p>To be precise, we are sharing the Network and Cache code. A couple of great introduction articles are at the end of the post. Below is the shared module initially looked at.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/561/1*nlQDDg9wkQuNb46aC3TMxA.png" /></figure><p>So why modularise the shared module?</p><p>Well, I was initially using packages to structure code. It worked, but it was a pain navigating through packages. Since I plan on updating this project, I need to do things correctly. And that, folks, is why we are here.</p><p>One other thing, any time I change a class in the shared module, the whole module is built again.</p><h3>Going Modular</h3><p>The image below shows what the shared module looks like after a couple of experiments here and there. But first, let’s take a look at the project structure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/497/1*UPOZpV5MDdU9mVTw2g3-IA.png" /><figcaption>Shared module structure</figcaption></figure><h3>Project Structure</h3><p>We have a couple of modules but can group them into four main directories/modules.</p><ul><li>base: This module contains iOS `ApplicationComponent` classes and configuration to export the ios framework.</li><li>core:database: DB implementation. We are using <a href="https://cashapp.github.io/sqldelight/">SQLDelight</a> for this project.</li><li>core:datastore: DB implementation. We are using <a href="https://cashapp.github.io/sqldelight/">SQLDelight</a> for this project.</li><li>core:networkutil: Contains all &quot;common&quot; classes used by all modules. These can be util classes like CoroutineScope/Dispatchers, util classes, e.t.c.</li><li>core:api: Module with <a href="https://ktor.io/">Ktor</a> implementation for each respective platform. TMBD and Trakt.</li><li>data: It contains a repository, data sources, and model classes</li><li>domain: It contains presentation implementation. In this case, we use <a href="https://github.com/freeletics/FlowRedux">FlowRedux</a> to create a shared `state-machine` for both clients.</li></ul><h3>Kmm precompiled script</h3><p>Since most of the module’s <strong><em>build.gradle</em></strong> files are the same, we can use a precompiled script to save a lot of repetition. We create a file <strong><em>kmm-domain-plugin.gradle.kts</em></strong> in the <strong>buildSrc</strong> directory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MPtqJUe0UBtcAoepfFG49A.png" /><figcaption>kmm precompiled-script</figcaption></figure><p>We can then add the plugin to the module and get rid of a lot of code. Now, we need to apply the script and add the module dependencies. As you can see, one advantage of this is having lean modules and don’t need to have unnecessary dependencies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nCm_h-y99tYEU9pwt-qjMQ.png" /><figcaption>API build.gradle.kts</figcaption></figure><h3>Exposing dependencies on iOS</h3><p>Now that we have our modules, we need to add them to the shared module since this is the entry point for iOS. By adding the modules to commonMain, Kmm will generate an Obj-C Framework.</p><p>To expose the dependencies, we do two things</p><ol><li>Add the modules as API in commonMain source set</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fhvO_IDDDvQJvgQQLTq38A.png" /></figure><p>2. Export the modules in the Framework configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CeoaEUtmbSRTYxY9JtfZ_Q.png" /></figure><p>You’ll notice we are only adding API modules to the framework configuration. This is because we only need to expose the interfaces.</p><p>One last thing, and we can wrap things up. Let’s look at Dependency injection.</p><h3>Dependency injection</h3><p>I initially had what I wanted to call a mixed injection where Android uses <a href="https://developer.android.com/training/dependency-injection/hilt-android">Dagger Hilt</a>, and the Shared module uses <a href="https://insert-koin.io/">Koin</a> to initialize the graph. I migrated the implementation to <a href="https://github.com/evant/kotlin-inject/blob/main/README.md">kotlin-inject</a> by Eva Tatarka.</p><p><a href="https://github.com/evant/kotlin-inject/blob/main/README.md">kotlin-inject/README.md at main · evant/kotlin-inject</a></p><p>Most modules have a <strong>di</strong> the package that contains module dependencies. As an example, this is what genre looks like: I might have yet to mention that I&#39;m creating a Swift package and adding that to the iOS app. <a href="https://twitter.com/joreilly">John O&#39;Reilly</a> has a fantastic article on how to go about it. Read more <a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/">here</a>. (Follow him if you want to get lost in the KMM universe. He does cover a lot of topics and shares valuable resources.)</p><h3>Summary</h3><p>I hope this was useful as I walked you through how I approached this challenge. I know I could have gone deeper into things. Mostly because I’m sure, there are other ways of doing this. Better/more straightforward ways. If you have some insights, please feel free to reach out. Learning is an endless loop, and I’m up for it.</p><p>What I love about Kotlin Multiplatform is:</p><ul><li>Most of the core business logic is shared. This allows me to spare some time and dive into the Apple world.</li><li>UI development is Native. So I can still keep up on what’s happening on Android and learn a bit of Swift while at it.</li></ul><p>Until we meet again. Adios.</p><h3>Post Series</h3><ol><li><a href="https://medium.com/@code_wizard/kmm-preferences-datastore-674382443262">KMP Preferences Datastore</a></li><li><a href="https://medium.com/@code_wizard/kmm-environment-variables-part-1-877091c2d5b4">KMP Environment Variables (Part 1)</a></li><li><a href="https://medium.com/@_thomaskioko/intercepting-ktor-network-responses-in-kotlin-multiplatform-32946b7d4d65">Intercepting Ktor Network Responses in Kotlin Multiplatform.</a></li></ol><h3>Resources</h3><ul><li><a href="https://kotlinlang.org/docs/kmm-understand-project-structure.html">Understand the KMM project structure.</a></li><li><a href="https://youtu.be/hrRqX7NYg3Q">Kotlin Multiplatform In Production — Kevin Galligan</a></li><li><a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/">Using Swift Packages in a Kotlin Multiplatform project — John O’Reilly</a></li><li><a href="https://github.com/touchlab/KaMPKit">Touchlab — KaMPKit</a></li><li><a href="https://touchlab.co/multiple-kotlin-frameworks-in-application/">Multiple Kotlin Frameworks in an Application — Kevin Schildhorn</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nCm_h-y99tYEU9pwt-qjMQ.png" /><figcaption>API build.gradle.kts</figcaption></figure><h3>Exposing dependencies on iOS</h3><p>Now that we have our modules, we need to add them to the shared module since this is the entry point for iOS. By adding the modules to commonMain, Kmm will generate an Obj-C Framework.</p><p>To expose the dependencies, we do two things</p><ol><li>Add the modules as API in commonMain source set</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fhvO_IDDDvQJvgQQLTq38A.png" /></figure><p>2. Export the modules in the Framework configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CeoaEUtmbSRTYxY9JtfZ_Q.png" /></figure><p>You&#39;ll notice we are only adding API modules to the framework configuration. This is because we only need to expose the interfaces.</p><p>One last thing, and we can wrap things up. Let&#39;s look at Dependency injection.</p><h3>Dependency injection</h3><p>I initially had what I wanted to call a mixed injection where Android uses <a href="https://developer.android.com/training/dependency-injection/hilt-android">Dagger Hilt</a>, and the Shared module uses <a href="https://insert-koin.io/">Koin</a> to initialize the graph. I migrated the implementation to <a href="https://github.com/evant/kotlin-inject/blob/main/README.md">kotlin-inject</a> by Eva Tatarka.</p><p><a href="https://github.com/evant/kotlin-inject/blob/main/README.md">kotlin-inject/README.md at main · evant/kotlin-inject</a></p><p>Most modules have a <strong>di</strong> the package that contains module dependencies. As an example, this is what genre looks like: I might have yet to mention that I&#39;m creating a Swift package and adding that to the iOS app. <a href="https://twitter.com/joreilly">John O&#39;Reilly</a> has a fantastic article on how to go about it. Read more <a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/">here</a>. (Follow him if you want to get lost in the KMM universe. He does cover a lot of topics and shares valuable resources.)</p><h3>Summary</h3><p>I hope this was useful as I walked you through how I approached this challenge. I know I could have gone deeper into things. Mostly because I&#39;m sure, there are other ways of doing this. Better/more straightforward ways. If you have some insights, please feel free to reach out. Learning is an endless loop, and I&#39;m up for it.</p><p>What I love about Kotlin Multiplatform is:</p><ul><li>Most of the core business logic is shared. This allows me to spare some time and dive into the Apple world.</li><li>UI development is Native. So I can still keep up on what&#39;s happening on Android and learn a bit of Swift while at it.</li></ul><p>Until we meet again. Adios.</p><h3>Post Series</h3><ol><li><a href="https://medium.com/@code_wizard/kmm-preferences-datastore-674382443262">KMP Preferences Datastore</a></li><li><a href="https://medium.com/@code_wizard/kmm-environment-variables-part-1-877091c2d5b4">KMP Environment Variables (Part 1)</a></li><li>KMP: Intercepting Ktor Network Responses.</li></ol><h3>Resources</h3><ul><li><a href="https://kotlinlang.org/docs/kmm-understand-project-structure.html">Understand the KMM project structure.</a></li><li><a href="https://youtu.be/hrRqX7NYg3Q">Kotlin Multiplatform In Production — Kevin Galligan</a></li><li><a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/">Using Swift Packages in a Kotlin Multiplatform project — John O&#39;Reilly</a></li><li><a href="https://github.com/touchlab/KaMPKit">Touchlab — KaMPKit</a></li><li><a href="https://touchlab.co/multiple-kotlin-frameworks-in-application/">Multiple Kotlin Frameworks in an Application — Kevin Schildhorn</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=132c3dee6c95" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/going-modular-the-kotlin-multiplatform-way-132c3dee6c95">Going Modular — The Kotlin Multiplatform Way</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Gradle Version Catalog + Pre-Compiled Scripts]]></title>
            <link>https://medium.com/code-wizard/gradle-version-catalog-pre-compiled-scripts-419a7d1c11a9?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/419a7d1c11a9</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[gradle]]></category>
            <category><![CDATA[version-catalog]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Mon, 17 May 2021 14:52:05 GMT</pubDate>
            <atom:updated>2021-06-03T17:35:18.778Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/876/0*IIYvJunIuodu2aFG.png" /></figure><p>Gradle 7.0 introduced an incubating feature to centrally manage dependencies in your Gradle project.</p><blockquote>A <em>version catalog</em> is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.</blockquote><blockquote>Gradle documentaiton</blockquote><p>In this post, we will take a look at how to setup the versioning system and implement a workaround to use the catalog in pre-compiled scripts as this is not currently supported.</p><p>You can find the demo project <a href="https://github.com/c0de-wizard/grade-catalog-demo">here</a>.</p><p><strong>Common Dependency Magangement Patterns</strong></p><p>There have been different patterns developers have come up with to help with dependency declaration and management.</p><ul><li>Using versions in properties files gradle.properties</li><li>Creating a gradle file. dependencies.gradle</li><li>Kotlin DSL dependencies.kts This is what we will use as an example in this post</li></ul><p>Since the rise of Kotlin DSL where you declare libraries in buildSrc, I have never looked back, well until 7.0 😂. Basically declaring dependencies in buildSrc then using type-sage accessors to declare dependencies in the build scripts.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f8e0697d25876e6099f2bbe50ed03c50/href">https://medium.com/media/f8e0697d25876e6099f2bbe50ed03c50/href</a></iframe><p>We could then go ahead and use this in our build script like so:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e6ba754b8d643277d5887efd826db918/href">https://medium.com/media/e6ba754b8d643277d5887efd826db918/href</a></iframe><p>One drawback of this method is any change to the dependencies will trigger recompilation of build scripts and invalidate the build script classpath. This will end up rebuilding a lot more than what you should do for a version change.</p><p><strong>Hello Version Catalogs</strong></p><p>A version catalog is basically a replacement for all the previous patterns, supported by Gradle, without the drawbacks of the previous approaches.</p><p><strong>The version catalog TOML file</strong></p><p>Gradle offers a conventional file to declare a catalog. We’ll create a file libs.versions.toml in gradle subdirectory of the root build directory and store all our dependencies and version in there.</p><p>The <a href="https://toml.io/">TOML</a> file consists of 3 major sections:</p><ul><li><strong>[versions]</strong> used to declare versions that can be referenced by dependencies.</li><li><strong>[libraries]</strong> used to declare the aliases to coordinates.</li><li><strong>[bundles]</strong> used to declare dependency bundles.</li></ul><p><strong>Enable version catalog</strong></p><p>To enable the feature to add the below line in your root project’s settings.gradle.kts</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/193cd77ef7923bb5ca1e0fd1a524bedc/href">https://medium.com/media/193cd77ef7923bb5ca1e0fd1a524bedc/href</a></iframe><p><strong>Declaring Dependencies &amp; Versions</strong></p><p>We can now go ahead and add dependencies to the toml file like so.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4b29adb10a717032b5b379b5ce9f835b/href">https://medium.com/media/4b29adb10a717032b5b379b5ce9f835b/href</a></iframe><p><strong>Referencing Dependencies</strong></p><p>We can now reference dependency in the build.gralde.kts. Instead of referencing the dependency name and version from the dependency file, we had earlier can simply use it in the dependency block libs.dependencyName</p><p>One slight thing to note is androidx-appCompat (<strong>toml</strong>) becomes libs.androidx.appCompat (<strong>kts</strong>). I find this a good way of nesting/grouping dependencies.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bf2f0b5b2151c41b796b2668bda410fb/href">https://medium.com/media/bf2f0b5b2151c41b796b2668bda410fb/href</a></iframe><p>We now have a single place where you can update everything, dependencies, and versions.</p><p><strong>Enable version catalog for pre-compiled scripts</strong></p><p>So far, this will work if you are just using the version catalog in the root build.gradle.ktsfile. We need to add one more thing to allow us to use the catalog system in our pre-compiled scripts. So let&#39;s go ahead and fix that.</p><p>Create settings.gradle.kts inside buildSrc directory and add the following piece of code.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/45109cc5c0ebae4939a85095cff64ca1/href">https://medium.com/media/45109cc5c0ebae4939a85095cff64ca1/href</a></iframe><p><a href="https://github.com/gradle/gradle/issues/15383">This</a> is a known issue and we will probably get rid of this code once the issue is resolved.</p><p>That’s it. You can now use the version catalog in your pre-compiled scripts. You can take a look at the <a href="https://docs.gradle.org/7.0-rc-1/userguide/platforms.html#sub:central-declaration-of-dependencies">documentation</a> for further details</p><p><em>Originally published at </em><a href="https://www.thomaskioko.com/2021/05/17/gradle-version-catalog-pre-compiled-scripts/"><em>Thomas Kioko</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=419a7d1c11a9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/code-wizard/gradle-version-catalog-pre-compiled-scripts-419a7d1c11a9">Gradle Version Catalog + Pre-Compiled Scripts</a> was originally published in <a href="https://medium.com/code-wizard">code_wizard</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Animation Magic With MotionLayout]]></title>
            <link>https://medium.com/@_thomaskioko/animation-magic-with-motionlayout-e640b31d53e1?source=rss-f0e2842a5e16------2</link>
            <guid isPermaLink="false">https://medium.com/p/e640b31d53e1</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[constraintlayout]]></category>
            <category><![CDATA[android-animations]]></category>
            <category><![CDATA[motionlayout]]></category>
            <dc:creator><![CDATA[Thomas Kioko™]]></dc:creator>
            <pubDate>Fri, 21 Sep 2018 03:50:49 GMT</pubDate>
            <atom:updated>2019-05-22T20:15:52.116Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*By2IYK-c5chgcmmXOcQOkQ.png" /></figure><p>Ladies and gentlemen, if you have not checked out ConstraintLayout 2.0, you are missing out. I have been playing around with it and I’m at the point of no return. Ever wanted to make cool animations? Well, it’s a walk in the park. You just need to know where the park is and how big it is. 😏</p><h4>About ConstraintLayout</h4><p>ConstraintLayout 2.0 was announced at <a href="https://www.youtube.com/watch?v=W39H7972buY">Google I/O 2018</a>. What got me excited was MotionLayout, a tool for layout animations. Yes, you read that right. Layout Animations. In this series well take a look at how we can use motion layout to create a Twitter Profile like UI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*LJrkDmyj1P9vDR3IoefZNg.gif" /></figure><p>Yap… All this was done using XML. 😃I’d recommend that you check out <a href="https://medium.com/google-developers/introduction-to-motionlayout-part-i-29208674b10d">this introduction</a> by <a href="https://twitter.com/camaelon">Nicolas Roard</a>.</p><h4>Intro</h4><p>Here’s a break down:</p><ul><li>Adding ConstraintLayout 2.0 &amp; MotionLayout</li><li>Motion Layout</li><li>MotionScene</li><li>OnSwipe</li><li>Transition</li><li>ConstraintSets</li><li>KeyFrames</li><li>Conclusion</li></ul><h4>Adding MotionLayout</h4><p>To add motion layout to your project, simply add the following to your Gradle file:</p><h4>MotionLayout</h4><p>As the name says, it’s Motion + Layout files. Motion layout builds upon ConstraintLayout. It allows us to handle layout transitions and complex motion. Think of it this way, ConstraintLayout allows us to create complex layouts and MotionLayout is the icing on the cake adding animations to the layout.</p><p>So let’s start by setting up the UI using ConstraintLayout. Allow me to skip this part or I’ll end up talking about ConstraintLayouts. I’ll post some links on ConstraintLayouts at the end 😃</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*g_1vxmJUdb7MmaEs.jpg" /><figcaption>Fast forward creating the layout….</figcaption></figure><h4>Voila …</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2NYaa7__LOVWMLkU.png" /><figcaption>ConstraintLayout</figcaption></figure><p>Something to take note of, MotionLayout is a subclass of ConstraintLayout. So we need to make sure our root tag is MotionLayout.</p><h4>MotionScene</h4><p>We’ll a separate XML file inside res/xml that will contain MotionLayout information then we will reference the file in out activity file like so.</p><p>app:layoutDescription=&quot;@xml/scene_twitter_profile&quot;</p><p>The MotionScene contains the magic.</p><h4>Transition</h4><p>We’ll start by checking out the Transition Element. This defines what the start and end states are. We define the states using the respective attributes: constraintSetEnd and constraintSetStart. The ids of the states can be anything, the same way we name views.</p><h4>OnSwipe</h4><p>OnSwipe is contained inside the Transition definition. It lets you manage the transition by matching the motion of the user’s finger. There are 3 main params:</p><ul><li>dragDirection: Think of this as the scroll direction (top / bottom / left / right ).</li><li>touchAnchorId: This is the view we are tracking, in our case it will be the RecylerView. When the user scrolls up or down (dragDirection) do some magic</li><li>touchAnchorSide: the side of the object that should track your finger (right /left /bottom /top)</li></ul><p>This is how it looks</p><p>We are almost there… Stay with me. 🤓</p><h4>ConstraintSet</h4><p>We will use ConstraintSets to handle our states. To do so, we will create two ConstraintSets or should I say we’ll create a set. 😏No? Fine…</p><p>We can now add constraints for the views in the layout file. You will notice that it looks like a normal file just that we don’t specify the widget.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*ECKyA9y351IUsJ6a.gif" /></figure><p>With only those lines of code, we get the following effect. We are not there yet but this is progress. We need to fix a couple of things but before we do that, let’s see what’s happening.</p><p>There are 3 things to take note of:</p><ol><li><strong>Attributes Override</strong>: Defined attributes in the scene file will override those in the layout file. ( <strong>layout_height, padding, margin, constraints</strong> …)So you can actually get rid of them in the layout file making it cleaner. I really like this</li><li><strong>ConstraintSet values</strong>: If you take a closer look at the layout height of the toolbar, you’ll see there’s a difference. The first one defines the size of the image in its original state (expanded) and the second one in its final state (collapsed). This gives us a collapsing bar effect when we scroll.</li><li><strong>CustomAttributes</strong>: We have access to various attributes that allow us to modify a widget. In this tutorial, we are using imageAlpha to modify the transparency of the image. You can read more about <a href="https://medium.com/google-developers/introduction-to-motionlayout-part-ii-a31acc084f59">CustomAttributes here</a>.</li></ol><h4>Image Fix</h4><p>When we scroll up, we want the image to scroll under the toolbar. To do this we simply change the elevation of the toolbar in the final state (collapsed). We update it from 2dp to 4dp. JUST that and …. Boom!!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*emdXEnWsH1JhPup9.gif" /></figure><h4>KeyFrameSet</h4><p>Before we wrap this up, I’ll touch on KeyFrameSets. They let us change a widget at a certain point during the transition. In our case, we want to shrink the profile image when scrolling up and down.</p><p>The KeyFrameSet contains KeyAttribute. <strong><em>framePosition</em></strong> specifies when we want to modify the image. It takes in a value between 0–100. In our case, when we are 20% into the transition, start to scale down the image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*P_dpDmNucwPzQxzR.gif" /></figure><p>Still, on KeyFrames, we want the UserName and tweet count to appear on the toolbar. We’ll update the alpha value of the TextViews to “1” when we are 100% into the transition. This will make them visible since we’ve set the widgets to invisible by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*68f5qHKEY4_I1i-O.png" /></figure><h4>Final Result</h4><p>After patching it all up, we get this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*LJrkDmyj1P9vDR3IoefZNg.gif" /></figure><h4>Conclusion</h4><p>You can see how MotionLayout is amazing and powerful. <strong>No Java/Kolin code was used</strong>. Let that sink in.</p><p>It goes to show the power of MotionLayout. I’m really looking forward to the UI editor and other amazing updates.</p><p>The source code for this article is available on <a href="https://github.com/kioko/motion-layout-playground">Github</a></p><h4>Resources</h4><ol><li><a href="https://developer.android.com/training/constraint-layout/">Build a Responsive UI with ConstraintLayout</a></li><li><a href="https://app.pluralsight.com/library/courses/android-fundamentals-constraint-layout/table-of-contents">Android Fundaments: ConstraintLayout</a> by <a href="https://medium.com/u/3f9b9c30bec7">Rebecca Franks</a></li><li><a href="https://codelabs.developers.google.com/codelabs/constraint-layout/index.html?index=..%2F..%2Fio2018#0">ConstraintLayout CodeLab</a></li></ol><p><strong>Thank you for using your precious time to read this article.</strong> <strong>Liked it? Clap 👏 to say “thanks!” and please share it to help others find this article.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e640b31d53e1" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>