Development rules and best practices for the Trick Kotlin Multiplatform messaging application.
- Use multiplatform-friendly APIs - Prefer libraries that work across Android and iOS
- Use
expect/actualdeclarations for platform-specific implementations- Define
expectincommonMain, provideactualinandroidMainandiosMain - Keep platform-specific code isolated to platform source sets
- Define
- Avoid platform-specific dependencies in common code - Only use multiplatform libraries in
commonMain - Use Kotlin Coroutines and Flow for asynchronous operations
All code in composeApp/src/iosMain/ MUST compile with Xcode's toolchain.
- NO Java APIs:
java.*andjavax.*are NOT available on iOS - NO Android APIs: Android-specific imports are NOT available
- Use iOS platform APIs:
platform.Foundation.*for NSString, NSData, NSDate, NSUUID, NSFileManager, etc.platform.CoreCrypto.*for cryptographic operations (notjava.security.*)platform.Darwin.*for Darwin-specific APIskotlinx.cinterop.*for C FFI (requires@OptIn(ExperimentalForeignApi::class))
- Common mappings:
- URL encoding:
NSString.stringByAddingPercentEncodingWithAllowedCharacters(...) - UUID:
NSUUID().UUIDString() - File I/O:
NSFileManager.defaultManagerandNSData - Hashing:
platform.CoreCrypto.CC_SHA256withkotlinx.cinterop - Secure storage:
platform.Security.*(Keychain) orNSUserDefaults
- URL encoding:
- Always check imports - if you see
java.*orjavax.*iniosMain, it's wrong! - Reference existing code: Check
composeApp/src/iosMain/for patterns
- Java standard library available:
java.net.*,java.io.*,java.security.*,java.util.* - Use Android-specific APIs only in
androidMain - Request permissions using
rememberLauncherForActivityResult
- Rust FFI Layer: All Signal Protocol operations through
rust/trick-signal-ffi - Official Library: libsignal v0.86.7 from
https://github.com/signalapp/libsignal - Platform Bridges:
- Android: JNI bridge (
SignalNativeBridge.android.kt) →libtrick_signal_ffi.so - iOS: C interop bridge (
SignalNativeBridge.ios.kt) →libtrick_signal_ffi.a
- Android: JNI bridge (
- Common Interface:
SignalNativeBridge(expect object) incommonMain - Wrapper:
LibSignalManagerdelegates toSignalNativeBridgefor both platforms
- Kyber Post-Quantum Cryptography: Required (libsignal 0.86.7+ mandates Kyber prekeys)
- All cryptographic operations must go through
SignalNativeBridge- never implement custom crypto - Test encryption/decryption on both platforms after any changes
- Android:
./gradlew buildRustAndroidorrust/trick-signal-ffi/build-android.sh- Outputs:
libtrick_signal_ffi.sotocomposeApp/src/androidMain/jniLibs/
- Outputs:
- iOS:
rust/trick-signal-ffi/build-ios.sh- Outputs: XCFramework and static libraries
- Note: Rust library must be built before running the app (not auto-built by Gradle)
- Use Koin for multiplatform DI
- Common module in
commonMain/kotlin/org/trcky/trick/di/Koin.kt - Platform-specific modules passed to
initKoin()from platform entry points
- Use SQLDelight - multiplatform and type-safe
- Define schemas in
commonMain/sqldelight/ - Use coroutines extensions for reactive queries
- Always use migrations for schema changes - never modify existing schemas directly
- Use Compose Multiplatform for all UI code
- Keep UI code in
commonMainwhen possible - Follow Material Design 3 guidelines
- Networking: Ktor Client (Android:
ktor-client-okhttp, iOS:ktor-client-darwin) - Serialization: Kotlinx Serialization for JSON
- Reactive: Kotlin Coroutines and Flow (prefer
StateFlowfor UI state) - Error Handling: Use Kotlin
Resulttypes, never expose platform-specific exceptions in common interfaces
trick/
├── composeApp/src/
│ ├── commonMain/ # Shared business logic, schemas, resources
│ ├── androidMain/ # Android implementations, jniLibs/
│ ├── iosMain/ # iOS implementations
│ └── nativeInterop/ # C interop definitions
└── rust/trick-signal-ffi/ # Rust FFI crate (jni_bridge.rs, ffi.rs, ops.rs)
- Platform-specific files: Use
.android.ktand.ios.ktsuffixes - Package structure:
org.trcky.trick.<module> - Expect/Actual Pattern: Define
expectincommonMain, implementactualin platform source sets
- Always use Signal Protocol for message encryption - never implement custom crypto
- Store sensitive data securely: Android (EncryptedSharedPreferences/Keystore), iOS (Keychain)
- Validate all inputs before processing
- Never log sensitive data (keys, messages, user data)
- Use HTTPS for network communication (when not peer-to-peer)
- Use coroutines for async - avoid blocking threads
- Profile on both platforms - performance characteristics may differ
- Prefer multiplatform libraries over platform-specific ones
- Keep versions in
gradle/libs.versions.toml - Rust Dependencies: libsignal v0.86.7, JNI v0.21 (Android), cbindgen v0.27, Rust 1.85+
- Write unit tests in
commonTestfor shared business logic - Test platform-specific implementations separately
- Test encryption/decryption flows on both platforms
- Write self-documenting code with clear names
- Add KDoc comments for public APIs
- Keep functions small and focused
- Java APIs in iOS code -
java.*andjavax.*imports will NOT compile on iOS - Custom encryption - Always use Signal Protocol via
SignalNativeBridge(Rust FFI) - Forgetting to build Rust library - Must build before running app (not auto-built)
- Platform-specific code in commonMain - Use expect/actual pattern
- Missing Kyber prekeys - libsignal 0.86.7+ requires Kyber prekeys in PreKey bundles
- Rust FFI signature changes - Must update both Android (JNI) and iOS (C FFI) bridges
- Define
expectincommonMain - Implement
actualinandroidMainandiosMain - Add to DI module if needed
- Test on both platforms
# Android
./gradlew buildRustAndroid
# iOS
cd rust/trick-signal-ffi && ./build-ios.sh- Update
.sqfile incommonMain/sqldelight/ - Create migration
- Test on both platforms
- No
java.*orjavax.*imports - No Android-specific imports
- Use
platform.Foundation.*for iOS APIs - Use
kotlinx.cinterop.*for C FFI (with@OptIn(ExperimentalForeignApi::class)) - Use
platform.CoreCrypto.*for crypto (notjava.security.*) - Check existing
iosMainimplementations for patterns
When in doubt:
- Check existing code patterns in the codebase
- Verify multiplatform compatibility of libraries
- Test on both Android and iOS
- Follow the expect/actual pattern for platform differences