Skip to content

Template for creating Kotlin Multiplatform applications at Futured.

License

Notifications You must be signed in to change notification settings

futuredapp/kmp-futured-template

Repository files navigation

Futured KMP Template

Hey there 👋

This is our template from which we build Kotlin Multiplatform applications that target Android and iOS platforms. It is our opinionated way of building KMP apps and shines a light on how we structure our architecture and what tools and libraries we use.

To give you a short overview of our stack, we use:

  • Native UI on both platforms. Jetpack Compose on Android and SwiftUI on iOS. The rest of the application is shared in KMP.
  • Decompose for sharing presentation logic and navigation state.
  • The presentation layer follows the MVI-like design pattern.
  • Koin for dependency injection.
  • SKIE for better Kotlin->Swift interop (exhaustive enums, sealed classes, Coroutines support).
  • moko-resources for sharing string (and other types of) resources.
  • apollo-kotlin network client for apps that call GraphQL APIs.
  • ktorfit network client for apps that call plain HTTP APIs.
  • Jetpack DataStore as a simple preferences storage (we have JSON-based and primitive implementations).
  • iOS-templates as template which generates a new iOS scene using MVVM-C architecture.

The template is a sample app with several screens to let you kick off the project with everything set up, incl. navigation and some API calls.

-------8<------- CUT HERE AFTER CLONING -------8<-------

Project Name

kmp compose swiftui

kotlin-version android-minsdk android-targetsdk ios-target

Short project description.

Project info

KMP

  • Product Flavors: dev, prod
  • Use-Cases: Kotlin Coroutines cr-usecases

Android

  • ApplicationId: app.futured.project
  • minSdk: 28
  • targetSdk: 34
  • Supports: Dark mode, landscape orientation
  • Build Variants: debug, enterprise, release

iOS

  • Deployment target: 16.0
  • Bundle identifier: app.futured.project
  • Supports: Dark mode, landscape orientation, iPadOS, watchOS
  • Language: Swift 5.0
  • IDE: Xcode 11.0
  • Dependency management: Swift package manager
  • Command line tools: Fastlane
  • Code style:

Team:

Used Tools

Test accounts

Security Standard

This project complies with Standard (F0), High (F1), Highest (F2) security standard.

Project specific standard

Gradle tasks

  1. clean - Remove all build folders
  2. lintCheck - Run ktlint, detekt checks. The same runs on CI.
  3. ktlintFormat - Reformat source code according to ktlint rules.
  4. assembleAndCopyDebugSwiftPackage - Assemble debug KMP XCFramework and copy it into local iOS Swift Package as dependency. (This task shouldn't be used directly, build the KMP target in Xcode, instead.)
  5. assembleAndCopyReleaseSwiftPackage - Assemble release KMP XCFramework and copy it into local iOS Swift Package as dependency. (This task shouldn't be used directly, build the KMP target in Xcode, instead.)
  6. generateMRcommonMain - Regenerate shared resource IDs.
  7. :shared:network:graphql:downloadApolloSchemaFromIntrospection - Download the latest Apollo schema.
  8. :shared:network:graphql:generateApolloSources - Generate Apollo sources (rebuilds models after adding modifying queries, mutations, etc.).

Kotlin Multiplatform Swift Package Integration

Architecture Overview

The iOS application integrates with KMP code through a local Swift Package Manager (SPM) package located at iosApp/shared/KMP/. This package wraps the XCFramework generated by the KMP Gradle plugin, providing a clean and modern dependency management approach.

Key components:

  1. Swift Package: Located at iosApp/shared/KMP/ with Package.swift defining the binary target
  2. XCFramework: Generated by Gradle and referenced by the Swift Package
  3. Makefile: Automates the build process based on environment variables
  4. Build Configuration: Environment variables defined in .xcconfig files

Environment Variables

Two critical environment variables control the build process:

  • KMP_FRAMEWORK_BUILD_TYPE: Specifies the framework build type (debug or release)

    • Set in .xcconfig files for each Xcode build configuration
    • Controls optimization level and debug information
    • Determines which Gradle task to run (assembleAndCopyDebugSwiftPackage or assembleAndCopyReleaseSwiftPackage)
  • KMP_BUILD_FLAVOR: Specifies the product flavor (see Product Flavors)

    • Set in .xcconfig files for each Xcode build configuration
    • Controls API endpoints and environment-specific configuration
    • Passed to Gradle as -P buildkonfig.flavor=$(KMP_BUILD_FLAVOR)

Development Workflow

To build and update the KMP package during development:

  1. Select and build the "Build KMP" target in Xcode:

    • This target's build phase runs the Makefile with the appropriate environment variables
    • The Makefile detects the build configuration and runs the corresponding Gradle task
    • The XCFramework is generated and copied to the Swift Package location
  2. Alternative manual build:

    • ./gradlew assembleAndCopyDebugSwiftPackage
    • or if you wish to specify product flavor (default is always dev): ./gradlew assembleAndCopyDebugSwiftPackage -P buildkonfig.flavor=[dev|prod]
  3. After KMP code changes:

    • Always rebuild the "KMP Package" target to update the XCFramework
    • Then build the main app target to use the updated KMP code

Navigation Structure

The app utilizes Decompose to share presentation logic and navigation state in KMP.
The following meta-description provides an overview of Decompose navigation tree:

Navigation("RootNavHost") {
    Slot {
        Screen("LoginScreen")
        Navigation("SignedInNavHost") {
            // Bottom navigation stack
            Stack {
                // Home tab
                Navigation("HomeNavHost") {
                    Stack {
                        Screen("FirstScreen")
                        Screen("SecondScreen") {
                            Slot {
                                Screen("Picker")
                            }
                        }
                        Screen("ThirdScreen")
                    }
                }
                // Profile tab
                Navigation("ProfileNavHost") {
                    Stack {
                        Screen("ProfileScreen")
                    }
                }
            }
        }
    }
}

Project Setup

Initial script

Use init_template.kts script to set up the template. The script renames directories and package names in files to the given package name.

It is written in Kotlin. To run it you need to have kscript installed.

Usage

kscript init_template.kts

Product Flavors

The project utilizes BuildKonfig plugin for implementing build flavors in the network module. There are two product flavors: dev and prod, which select API url used in :shared:network:rest and :shared:network:graphql modules.

In general, the build flavor can be specified as a Gradle build flag

./gradlew whateverTask -P buildkonfig.flavor=dev

Please, refer to :shared:network:* module Gradle configs for more info.

Android

During local development, the build flavor can be specified in gradle.properties file like so:

buildkonfig.flavor=dev

Charles Proxy

Charles Proxy is enabled for debug and enterprise builds.

iOS

On iOS, we utilize .xcconfig Build Configuration files, where each file per build configuration specifies a KMP_BUILD_FLAVOR environment variable.

This variable is then used in the "Build KMP" target to pass the flavor as Gradle build flag.

./gradlew assembleAndCopyDebugSwiftPackage -P buildkonfig.flavor=$(KMP_BUILD_FLAVOR)

See Makefile for reference.

Currently, the Debug.xcconfig file sets KMP_BUILD_FLAVOR=dev and the Release.xcconfig sets KMP_BUILD_FLAVOR=prod. When adding new build configurations, please make sure to also define the KMP_BUILD_FLAVOR variable using the aforementioned method.

Crashlytics

We can have symbolicated Kotlin crash reports on iOS. We use NSExceptionKt to achieve that. Everything is set up, but some finishing touches need to be made when you add Crashlytics to your project:

  1. Set up Firebase Crashlytics on both platforms as you would usually do.
  2. After dependencies are in place, on each platform uncomment the code in PlatformFirebaseCrashlyticsImpl classes (follow comments).
  3. On iOS, do not forget to also upload debug symbols to Crashlytics. The KMP framework is static, so no standalone debug symbols are generated for KMP, instead, they are included in the app itself.

Deep Linking

Deep links are provided by each platform to common code and parsed using DeepLinkResolver class. The (sample) app currently supports the following url scheme: kmptemplate and the following links:

  • kmptemplate://home -- Opens Home tab with default stack.
  • kmptemplate://profile -- Opens Profile tab with default stack.
  • kmptemplate://home/second -- Opens SecondScreen in Home tab.
  • kmptemplate://home/third?arg={argument} -- Opens ThirdScreen in Home tab with provided argument. The argument is mandatory.
  • kmptemplate://home/third/{argument} -- Opens ThirdScreen in Home tab with provided argument. The argument is mandatory.