<?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 Faisal Ahmed on Medium]]></title>
        <description><![CDATA[Stories by Faisal Ahmed on Medium]]></description>
        <link>https://medium.com/@faisalahmedwork1?source=rss-e785afa712bc------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*TcP30TpBDLAoXB17jMNRuw.jpeg</url>
            <title>Stories by Faisal Ahmed on Medium</title>
            <link>https://medium.com/@faisalahmedwork1?source=rss-e785afa712bc------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 24 Apr 2026 17:06:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@faisalahmedwork1/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[Building Your First Kotlin Multiplatform App: From Setup to iOS App Store with Compose…]]></title>
            <link>https://blog.kotlin-academy.com/building-your-first-kotlin-multiplatform-app-from-setup-to-ios-app-store-with-compose-e4d3f4c6767c?source=rss-e785afa712bc------2</link>
            <guid isPermaLink="false">https://medium.com/p/e4d3f4c6767c</guid>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[compose-multiplatform]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Faisal Ahmed]]></dc:creator>
            <pubDate>Thu, 26 Jun 2025 21:16:45 GMT</pubDate>
            <atom:updated>2025-07-17T10:25:37.109Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e0Jy6chNShlIOGzHcDCRbA.jpeg" /></figure><h3>Building Your First Kotlin Multiplatform App: From Setup to iOS App Store with Compose Multiplatform (2025 Guide)</h3><blockquote><em>🎯 </em><strong><em>What You’ll Learn</em></strong><em>: Build a production-ready subscription tracker that runs natively on Android and iOS with 95% shared code, including database, business logic, and UI.</em></blockquote><blockquote><em>⏱️ </em><strong><em>Time Investment:</em></strong><em> ~3–4 hours for complete implementation</em></blockquote><blockquote><em>🎯 </em><strong><em>Target Audience:</em></strong><em> Android developers exploring KMP, iOS developers curious about Kotlin</em></blockquote><h3>🎯 Introduction</h3><p>Remember when building for both Android and iOS meant writing everything twice? Those days are over.</p><p>With Compose Multiplatform for iOS now stable (May 2025), you can build truly native apps with 95% shared code. No more maintaining two codebases, no more feature parity issues, no more “it works on Android but breaks on iOS.”</p><p>In this comprehensive guide, we’ll build <strong>Kyklos</strong> — a subscription management app that proves KMP is ready for production. We’ll tackle real challenges like cross-platform databases, Material Design on iOS, and the gotchas that can trip up new KMP developers.</p><p>By the end of this tutorial, you’ll have created a fully functional app with:</p><ul><li>✅ Shared business logic and UI between Android and iOS</li><li>✅ Clean Architecture with proper separation of concerns</li><li>✅ Type-safe database using SQLDelight</li><li>✅ Material Design 3 theming that works on iOS</li><li>✅ Production-ready iOS support with real device testing</li></ul><h3>🏗️ What We’re Building</h3><p><strong>Kyklos</strong> (Greek for “cycle”) is a subscription management app that helps users track their recurring payments. The app features:</p><ul><li>📱 Cross-platform native UI with shared Compose code</li><li>💰 Subscription tracking with intelligent cost calculations</li><li>🌍 Multi-currency support (USD, EUR, GBP, CAD, AUD, INR)</li><li>📊 Monthly spending summaries and analytics</li><li>🎨 Modern Material Design 3 interface</li><li>🔄 Real-time sync across platforms</li></ul><h3>🛠️ Setting Up the Development Environment</h3><h3>Prerequisites</h3><ul><li><strong>Android Studio</strong> with the latest KMP plugin</li><li><strong>Xcode 14+</strong> (for iOS development and simulator)</li><li><strong>JDK 11+</strong></li><li><strong>Kotlin 2.1.21+</strong></li></ul><h3>Creating the Project</h3><p>Start by creating a new Kotlin Multiplatform project:</p><pre># Option 1: Using the KMP wizard (Recommended)<br># Visit: https://kmp.jetbrains.com/<br># Configure: Android + iOS + Compose Multiplatform<br><br># Option 2: Android Studio<br># New Project &gt; Kotlin Multiplatform &gt; Mobile Application</pre><p><strong>🏗️ Architecture Decision:</strong> Why start with the official wizard? It sets up the proper expect/actual structure and configures Compose Multiplatform correctly for both platforms.</p><h3>📦 Dependencies and Project Structure</h3><h3>Core Dependencies</h3><p>Let’s set up our gradle/libs.versions.toml with production-ready versions:</p><pre>[versions]<br>kotlin = &quot;2.1.21&quot;<br>composeMultiplatform = &quot;1.8.2&quot;<br>sqlDelight = &quot;2.0.2&quot;<br>kotlinx-datetime = &quot;0.6.0&quot;<br>uuid = &quot;0.8.4&quot;<br><br>[libraries]<br># SQLDelight for cross-platform database<br>sqlDelight-driver-android = { module = &quot;app.cash.sqldelight:android-driver&quot;, version.ref = &quot;sqlDelight&quot; }<br>sqlDelight-driver-native = { module = &quot;app.cash.sqldelight:native-driver&quot;, version.ref = &quot;sqlDelight&quot; }<br>sqlDelight-runtime = { module = &quot;app.cash.sqldelight:runtime&quot;, version.ref = &quot;sqlDelight&quot; }<br>sqlDelight-coroutines = { module = &quot;app.cash.sqldelight:coroutines-extensions&quot;, version.ref = &quot;sqlDelight&quot; }<br><br># Cross-platform utilities<br>kotlinx-datetime = { module = &quot;org.jetbrains.kotlinx:kotlinx-datetime&quot;, version.ref = &quot;kotlinx-datetime&quot; }<br>uuid = { module = &quot;com.benasher44:uuid&quot;, version.ref = &quot;uuid&quot; }<br><br>[plugins]<br>sqlDelight = { id = &quot;app.cash.sqldelight&quot;, version.ref = &quot;sqlDelight&quot; }</pre><h3>Project Architecture</h3><p>We’ll implement <strong>Clean Architecture</strong> with clear layer separation:</p><pre>📱 Presentation Layer (UI)<br>├── 🎨 components/     # Reusable UI components<br>├── 📄 screens/        # Screen-level composables  <br>└── 🎭 theme/          # Material Design theme<br><br>💼 Domain Layer (Business Logic)<br>├── 📋 usecase/        # Business use cases<br>└── 📄 repository/     # Repository interfaces<br><br>💾 Data Layer (Data Management)  <br>├── 🗄️ local/          # SQLDelight database<br>├── 📊 repository/     # Repository implementations<br>└── 🔄 model/          # Data models</pre><p><strong>🏗️ Architecture Decision:</strong> Clean Architecture separates platform code from business logic, making testing easier and code more maintainable across Android and iOS.</p><h3>🗄️ Setting Up SQLDelight Database</h3><h3>1. Configure SQLDelight in build.gradle.kts</h3><pre>sqldelight {<br>    databases {<br>        create(&quot;KyklosDatabase&quot;) {<br>            packageName.set(&quot;dev.faisalahmed.kyklos.db&quot;)<br>            srcDirs(&quot;src/commonMain/sqldelight&quot;)<br>        }<br>    }<br>}</pre><h3>2. Create Database Schema</h3><p>Create composeApp/src/commonMain/sqldelight/database/Subscription.sq:</p><pre>-- Subscription table with proper indexing for performance<br>CREATE TABLE subscription (<br>    id TEXT NOT NULL PRIMARY KEY,<br>    name TEXT NOT NULL,<br>    cost REAL NOT NULL,<br>    currency_code TEXT NOT NULL,<br>    billing_cycle TEXT NOT NULL,<br>    next_payment_date INTEGER NOT NULL,<br>    is_active INTEGER NOT NULL DEFAULT 1,<br>    created_at INTEGER NOT NULL,<br>    updated_at INTEGER NOT NULL,<br>    notes TEXT,<br>    FOREIGN KEY (currency_code) REFERENCES currency(code)<br>);<br><br>-- Performance indexes for common queries<br>CREATE INDEX idx_subscription_active ON subscription(is_active);<br>CREATE INDEX idx_subscription_next_payment ON subscription(next_payment_date);<br><br>-- Query: Get all subscriptions with currency info<br>selectAll:<br>SELECT<br>    s.*,<br>    c.symbol AS currency_symbol,<br>    c.name AS currency_name,<br>    c.exchange_rate_to_usd<br>FROM subscription s<br>JOIN currency c ON s.currency_code = c.code<br>ORDER BY s.next_payment_date ASC;<br><br>-- Query: Get only active subscriptions<br>selectActive:<br>SELECT<br>    s.*,<br>    c.symbol AS currency_symbol,<br>    c.name AS currency_name,<br>    c.exchange_rate_to_usd<br>FROM subscription s<br>JOIN currency c ON s.currency_code = c.code<br>WHERE s.is_active = 1<br>ORDER BY s.next_payment_date ASC;<br><br>-- Insert new subscription<br>insertSubscription:<br>INSERT INTO subscription (<br>    id, name, cost, currency_code, billing_cycle,<br>    next_payment_date, is_active, created_at, updated_at, notes<br>) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);<br><br>-- Analytics query for monthly cost calculations<br>selectTotalMonthlyCostByActive:<br>SELECT<br>    s.currency_code,<br>    c.symbol AS currency_symbol,<br>    c.exchange_rate_to_usd,<br>    SUM(<br>        CASE<br>            WHEN s.billing_cycle = &#39;WEEKLY&#39; THEN s.cost * 4.33<br>            WHEN s.billing_cycle = &#39;MONTHLY&#39; THEN s.cost<br>            WHEN s.billing_cycle = &#39;QUARTERLY&#39; THEN s.cost / 3.0<br>            WHEN s.billing_cycle = &#39;SEMI_ANNUALLY&#39; THEN s.cost / 6.0<br>            WHEN s.billing_cycle = &#39;ANNUALLY&#39; THEN s.cost / 12.0<br>            ELSE s.cost<br>        END<br>    ) AS monthly_cost<br>FROM subscription s<br>JOIN currency c ON s.currency_code = c.code<br>WHERE s.is_active = ?<br>GROUP BY s.currency_code, c.symbol, c.exchange_rate_to_usd;</pre><h3>3. Cross-Platform Database Drivers</h3><p>The key to SQLDelight’s cross-platform magic is the <strong>expect/actual</strong> pattern:</p><p><strong>Common interface</strong> (commonMain/data/local/DatabaseDriverFactory.kt):</p><pre>import app.cash.sqldelight.db.SqlDriver<br><br>expect class DatabaseDriverFactory {<br>    fun createDriver(): SqlDriver<br>}</pre><p><strong>Android implementation</strong> (androidMain/data/local/DatabaseDriverFactory.android.kt):</p><pre>import android.content.Context<br>import app.cash.sqldelight.db.SqlDriver<br>import app.cash.sqldelight.driver.android.AndroidSqliteDriver<br>import dev.faisalahmed.kyklos.db.KyklosDatabase<br><br>actual class DatabaseDriverFactory(private val context: Context) {<br>    actual fun createDriver(): SqlDriver {<br>        return AndroidSqliteDriver(<br>            schema = KyklosDatabase.Schema,<br>            context = context,<br>            name = &quot;kyklos.db&quot;<br>        )<br>    }<br>}</pre><p><strong>iOS implementation</strong> (iosMain/data/local/DatabaseDriverFactory.ios.kt):</p><pre>import app.cash.sqldelight.db.SqlDriver<br>import app.cash.sqldelight.driver.native.NativeSqliteDriver<br>import dev.faisalahmed.kyklos.db.KyklosDatabase<br><br>actual class DatabaseDriverFactory {<br>    actual fun createDriver(): SqlDriver {<br>        return NativeSqliteDriver(<br>            schema = KyklosDatabase.Schema,<br>            name = &quot;kyklos.db&quot;<br>        )<br>    }<br>}</pre><h3>🏛️ Implementing Clean Architecture</h3><h3>1. Domain Layer — Business Logic</h3><p><strong>Repository Interface</strong> (domain/repository/SubscriptionRepository.kt):</p><pre>import dev.faisalahmed.kyklos.data.model.Subscription<br>import kotlinx.coroutines.flow.Flow<br><br>interface SubscriptionRepository {<br>    fun getAllSubscriptions(): Flow&lt;List&lt;Subscription&gt;&gt;<br>    fun getActiveSubscriptions(): Flow&lt;List&lt;Subscription&gt;&gt;<br>    suspend fun getSubscriptionById(id: String): Subscription?<br>    suspend fun insertSubscription(subscription: Subscription)<br>    suspend fun updateSubscription(subscription: Subscription)<br>    suspend fun deleteSubscription(id: String)<br>    suspend fun toggleSubscriptionStatus(id: String)<br>}</pre><p><strong>Use Case Example</strong> (domain/usecase/GetSubscriptionsUseCase.kt):</p><pre>class GetSubscriptionsUseCase(<br>    private val repository: SubscriptionRepository<br>) {<br>    operator fun invoke(activeOnly: Boolean = false): Flow&lt;List&lt;Subscription&gt;&gt; {<br>        return if (activeOnly) {<br>            repository.getActiveSubscriptions()<br>        } else {<br>            repository.getAllSubscriptions()<br>        }<br>    }<br>}</pre><h3>2. Data Layer — Repository Implementation</h3><pre>class SubscriptionRepositoryImpl(<br>    private val database: KyklosDatabase<br>) : SubscriptionRepository {<br><br>// Convert SQLDelight query results to domain models<br>    override fun getAllSubscriptions(): Flow&lt;List&lt;Subscription&gt;&gt; {<br>        return database.subscriptionQueries<br>            .selectAll()<br>            .asFlow()<br>            .mapToList(Dispatchers.IO)<br>            .map { rows -&gt;<br>                rows.map { row -&gt; row.toSubscription() }<br>            }<br>    }<br><br>    // Insert subscription with proper error handling<br>    override suspend fun insertSubscription(subscription: Subscription) {<br>        try {<br>            database.subscriptionQueries.insertSubscription(<br>                id = subscription.id,<br>                name = subscription.name,<br>                cost = subscription.cost,<br>                currency_code = subscription.currency.code,<br>                billing_cycle = subscription.billingCycle.name,<br>                next_payment_date = subscription.nextPaymentDate.toEpochMilliseconds(),<br>                is_active = if (subscription.isActive) 1L else 0L,<br>                created_at = subscription.createdAt.toEpochMilliseconds(),<br>                updated_at = subscription.updatedAt.toEpochMilliseconds(),<br>                notes = subscription.notes<br>            )<br>        } catch (e: Exception) {<br>            // Handle database errors appropriately<br>            throw DatabaseException(&quot;Failed to insert subscription: ${e.message}&quot;)<br>        }<br>    }<br>}</pre><h3>3. Data Models with Business Logic</h3><pre>data class Subscription(<br>    val id: String,<br>    val name: String,<br>    val cost: Double,<br>    val currency: Currency,<br>    val billingCycle: BillingCycle,<br>    val nextPaymentDate: Instant,<br>    val isActive: Boolean = true,<br>    val createdAt: Instant,<br>    val updatedAt: Instant,<br>    val notes: String? = null<br>) {<br>    // Business logic: Calculate monthly cost based on billing cycle<br>    fun calculateMonthlyCost(): Double {<br>        return billingCycle.calculateMonthlyCost(cost)<br>    }<br><br>    // Business logic: Convert to USD for unified calculations<br>    fun calculateMonthlyCostInUsd(): Double {<br>        val monthlyCost = calculateMonthlyCost()<br>        return currency.convertToUsd(monthlyCost)<br>    }<br><br>    // Formatting for display<br>    fun formatCost(): String {<br>        return currency.formatAmount(cost)<br>    }<br>    companion object {<br>        // Factory method for creating new subscriptions<br>        fun create(<br>            name: String,<br>            cost: Double,<br>            currency: Currency,<br>            billingCycle: BillingCycle,<br>            nextPaymentDate: Instant,<br>            notes: String? = null,<br>            now: Instant = Clock.System.now()<br>        ): Subscription {<br>            val id = uuid4().toString()<br>            return Subscription(<br>                id = id,<br>                name = name,<br>                cost = cost,<br>                currency = currency,<br>                billingCycle = billingCycle,<br>                nextPaymentDate = nextPaymentDate,<br>                createdAt = now,<br>                updatedAt = now,<br>                notes = notes<br>            )<br>        }<br>    }<br>}</pre><h3>🎨 Building the UI with Compose Multiplatform</h3><h3>1. Material Design 3 Theme</h3><pre>@Composable<br>fun KyklosTheme(<br>    darkTheme: Boolean = isSystemInDarkTheme(),<br>    content: @Composable () -&gt; Unit<br>) {<br>    val colorScheme = if (darkTheme) {<br>        DarkColorScheme<br>    } else {<br>        LightColorScheme.copy(<br>            primary = KyklosColors.KyklosPrimary,<br>            onPrimary = KyklosColors.KyklosOnPrimary,<br>            primaryContainer = KyklosColors.KyklosPrimaryContainer,<br>            onPrimaryContainer = KyklosColors.KyklosOnPrimaryContainer,<br>        )<br>    }<br><br>    // Modern rounded shapes that work well on both platforms<br>    val modernShapes = Shapes(<br>        extraSmall = RoundedCornerShape(8.dp),<br>        small = RoundedCornerShape(12.dp),<br>        medium = RoundedCornerShape(16.dp),<br>        large = RoundedCornerShape(24.dp),<br>        extraLarge = RoundedCornerShape(32.dp)<br>    )<br><br>    MaterialTheme(<br>        colorScheme = colorScheme,<br>        shapes = modernShapes,<br>        typography = KyklosTypography,<br>        content = content<br>    )<br>}</pre><h3>2. Reusable UI Components</h3><p><strong>Subscription Card Component:</strong></p><pre>@OptIn(ExperimentalMaterial3Api::class)<br>@Composable<br>fun SubscriptionCard(<br>    subscription: Subscription,<br>    onClick: () -&gt; Unit,<br>    modifier: Modifier = Modifier<br>) {<br>    Card(<br>        onClick = onClick,<br>        modifier = modifier.fillMaxWidth(),<br>        elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),<br>        colors = CardDefaults.cardColors(<br>            containerColor = MaterialTheme.colorScheme.surfaceVariant<br>        )<br>    ) {<br>        Column(<br>            modifier = Modifier.padding(16.dp)<br>        ) {<br>            // Header row with name and cost<br>            Row(<br>                modifier = Modifier.fillMaxWidth(),<br>                horizontalArrangement = Arrangement.SpaceBetween,<br>                verticalAlignment = Alignment.CenterVertically<br>            ) {<br>                Text(<br>                    text = subscription.name,<br>                    style = MaterialTheme.typography.titleMedium,<br>                    fontWeight = FontWeight.SemiBold,<br>                    maxLines = 1,<br>                    overflow = TextOverflow.Ellipsis,<br>                    modifier = Modifier.weight(1f)<br>                )<br><br>                Text(<br>                    text = subscription.formatCost(),<br>                    style = MaterialTheme.typography.titleMedium,<br>                    fontWeight = FontWeight.Bold,<br>                    color = MaterialTheme.colorScheme.primary<br>                )<br>            }<br><br>            Spacer(modifier = Modifier.height(8.dp))<br><br>            // Footer row with next payment and billing cycle<br>            Row(<br>                modifier = Modifier.fillMaxWidth(),<br>                horizontalArrangement = Arrangement.SpaceBetween,<br>                verticalAlignment = Alignment.CenterVertically<br>            ) {<br>                Text(<br>                    text = &quot;Next payment: ${subscription.nextPaymentDate.formatDate()}&quot;,<br>                    style = MaterialTheme.typography.bodyMedium,<br>                    color = MaterialTheme.colorScheme.onSurfaceVariant<br>                )<br><br>                Text(<br>                    text = subscription.billingCycle.displayName,<br>                    style = MaterialTheme.typography.labelMedium,<br>                    color = MaterialTheme.colorScheme.secondary<br>                )<br>            }<br>        }<br>    }<br>}</pre><h3>3. Screen-Level Composables</h3><pre>@OptIn(ExperimentalMaterial3Api::class)<br>@Composable<br>fun SubscriptionListScreen(<br>    subscriptions: List&lt;Subscription&gt; = emptyList(),<br>    totalMonthlyCost: String = &quot;$0.00&quot;,<br>    onAddSubscription: () -&gt; Unit = {},<br>    onSubscriptionClick: (Subscription) -&gt; Unit = {},<br>    modifier: Modifier = Modifier<br>) {<br>    Scaffold(<br>        topBar = {<br>            TopAppBar(<br>                title = {<br>                    Text(<br>                        text = &quot;Kyklos&quot;,<br>                        style = MaterialTheme.typography.headlineMedium,<br>                        fontWeight = FontWeight.Bold<br>                    )<br>                },<br>                colors = TopAppBarDefaults.topAppBarColors(<br>                    containerColor = MaterialTheme.colorScheme.primaryContainer,<br>                    titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer<br>                )<br>            )<br>        },<br>        floatingActionButton = {<br>            ExtendedFloatingActionButton(<br>                onClick = onAddSubscription,<br>                icon = {<br>                    Icon(<br>                        imageVector = Icons.Default.Add,<br>                        contentDescription = null<br>                    )<br>                },<br>                text = {<br>                    Text(&quot;Add Subscription&quot;)<br>                },<br>                containerColor = MaterialTheme.colorScheme.primary,<br>                contentColor = MaterialTheme.colorScheme.onPrimary<br>            )<br>        }<br>    ) { paddingValues -&gt;<br>        LazyColumn(<br>            modifier = modifier.padding(paddingValues),<br>            contentPadding = PaddingValues(16.dp),<br>            verticalArrangement = Arrangement.spacedBy(16.dp)<br>        ) {<br>            // Monthly summary card<br>            item {<br>                MonthlySummaryCard(<br>                    totalMonthlyCost = totalMonthlyCost,<br>                    subscriptionCount = subscriptions.size<br>                )<br>            }<br><br>            // Subscription list<br>            items(subscriptions) { subscription -&gt;<br>                SubscriptionCard(<br>                    subscription = subscription,<br>                    onClick = { onSubscriptionClick(subscription) }<br>                )<br>            }<br><br>            // Empty state<br>            if (subscriptions.isEmpty()) {<br>                item {<br>                    EmptyStateComponent(<br>                        message = &quot;No subscriptions yet&quot;,<br>                        actionText = &quot;Add your first subscription&quot;,<br>                        onActionClick = onAddSubscription<br>                    )<br>                }<br>            }<br>        }<br>    }<br>}</pre><h3>⚠️ iOS Compatibility: Critical Gotchas</h3><p>When developing for iOS with Kotlin Multiplatform, there are several compatibility gotchas to watch out for:</p><p><strong>💡 Pro Tip:</strong> These gotchas cost me hours of debugging. Learning them upfront will save you significant development time.</p><h3>1. String Formatting</h3><p>❌ <strong>Don’t use:</strong></p><pre>String.format(&quot;%.2f&quot;, value)  // Not available on iOS/KMP</pre><p>✅ <strong>Use instead:</strong></p><pre>// Option 1: String templates for simple cases<br>&quot;${currency.symbol}$cost&quot;<br><br>// Option 2: Expect/actual pattern for complex formatting<br>expect fun Double.formatCurrency(symbol: String): String<br><br>// Android actual<br>actual fun Double.formatCurrency(symbol: String): String =<br>    &quot;$symbol%.2f&quot;.format(this)<br><br>// iOS actual (using NSString)<br>actual fun Double.formatCurrency(symbol: String): String =<br>    NSString.init(format = &quot;$symbol%.2f&quot;, this).toString()<br><br>// Option 3: Manual precision handling (less preferred)<br>fun Double.toCurrencyString(symbol: String): String {<br>    val rounded = (this * 100).toLong() / 100.0<br>    return &quot;$symbol$rounded&quot;<br>}</pre><h3>2. UUID Generation</h3><p>❌ <strong>Don’t use:</strong></p><pre>UUID.randomUUID()  // Java UUID not available on iOS</pre><p>✅ <strong>Use instead:</strong></p><pre>import com.benasher44.uuid.uuid4<br>val id = uuid4().toString()  // Cross-platform UUID</pre><h3>🚀 Building and Running</h3><h3>Build Commands</h3><pre># Android<br>./gradlew compileDebugKotlinAndroid    # Compile Android code<br>./gradlew installDebug                 # Install on device/emulator<br><br># iOS (compilation check)<br>./gradlew compileKotlinIosX64         # Compile iOS code<br>./gradlew linkDebugFrameworkIosX64    # Link iOS framework<br><br># All platforms<br>./gradlew build                       # Build all targets<br><br># SQLDelight<br>./gradlew generateCommonMainKyklosDatabaseInterface<br><br># Clean build<br>./gradlew clean build</pre><h3>📱 Running on iOS Simulator</h3><p>To run your Kotlin Multiplatform app on the iOS Simulator:</p><h3>1. Generate iOS Framework</h3><pre># Build the iOS framework<br>./gradlew linkDebugFrameworkIosX64</pre><h3>2. Open iOS Project in Xcode</h3><pre># Navigate to iOS app directory<br>cd iosApp<br><br># Open in Xcode<br>open iosApp.xcodeproj</pre><h3>3. Configure and Run</h3><ol><li><strong>Select Target Device:</strong></li></ol><ul><li>In Xcode, click on the device selection dropdown</li><li>Choose your preferred iOS Simulator (e.g., iPhone 15, iPad Pro)</li></ul><p><strong>2. Build and Run:</strong></p><ul><li>Press Cmd + R or click the Run button</li><li>Xcode will build the project and launch the iOS Simulator</li></ul><h3>4. Development Workflow</h3><p>For efficient development:</p><ol><li>Make Kotlin changes in your shared code</li><li>Rebuild framework: ./gradlew linkDebugFrameworkIosX64</li><li>Run from Xcode to test changes on iOS Simulator</li><li>Use Compose Hot Reload when available for UI changes</li></ol><h3>5. Troubleshooting iOS Issues</h3><p><strong>Common Issues:</strong></p><ol><li><strong>Framework Not Found:</strong></li></ol><pre># Clean and rebuild framework<br>./gradlew clean<br>./gradlew linkDebugFrameworkIosX64</pre><p><strong>2. Simulator Not Starting:</strong></p><pre># Reset simulator<br>xcrun simctl erase all<br>xcrun simctl boot &quot;iPhone 15&quot;</pre><p><strong>3. Build Errors in Xcode:</strong></p><ul><li>Ensure your iOS deployment target matches your KMP project settings</li><li>Check that the framework is properly linked in Xcode project settings</li><li>Verify the framework search paths in Build Settings</li></ul><h3>🎯 Advanced Features and Next Steps</h3><h3>Current Implementation Status</h3><p>Based on the current Kyklos codebase, we have successfully implemented:</p><p>✅<strong> Core Features:</strong></p><ul><li>Cross-platform project setup with KMP</li><li>SQLDelight database configuration</li><li>Material Design 3 theming</li><li>Clean Architecture foundation</li><li>Basic UI components and screens</li></ul><h3>Immediate Next Steps</h3><ol><li><strong>Complete CRUD Operations</strong> — Full subscription management</li><li><strong>State Management</strong> — Add StateFlow and ViewModel integration</li><li><strong>Dependency Injection</strong> — Implement Koin for DI</li><li><strong>Navigation</strong> — Add Compose Navigation with type safety</li><li><strong>Error Handling</strong> — Comprehensive error states and recovery</li></ol><h3>Future Roadmap</h3><ol><li><strong>Networking</strong> — Add Ktor for API calls and sync</li><li><strong>Testing Suite</strong> — Comprehensive test coverage (planned for Part 5 of this series)</li><li><strong>CI/CD</strong> — GitHub Actions for automated builds and testing</li><li><strong>Platform Expansion</strong> — Web and Desktop support with Compose Multiplatform</li></ol><h3>📚 Key Takeaways</h3><p>Building a Kotlin Multiplatform app teaches several valuable lessons:</p><ol><li><strong>Architecture Matters</strong> — Clean Architecture provides excellent separation of concerns across platforms</li><li><strong>SQLDelight is Powerful</strong> — Type-safe SQL with excellent cross-platform support makes data management a breeze</li><li><strong>iOS Compatibility</strong> — Be mindful of platform-specific APIs and always test on both platforms early and often</li><li><strong>Compose Multiplatform</strong> — With iOS stable support, truly shared UI is now production-ready</li><li><strong>expect/actual Pattern</strong> — Elegant solution for platform-specific implementations when needed</li></ol><h3>🎯 Conclusion</h3><p>Kotlin Multiplatform Mobile has matured into a production-ready solution for cross-platform development. With Compose Multiplatform for iOS now stable, developers can share both business logic and UI code while maintaining native performance and platform-specific optimizations.</p><p>The <strong>Kyklos</strong> subscription management app demonstrates how to build a real-world application using modern KMP practices. By following Clean Architecture principles and leveraging powerful libraries like SQLDelight, you can create maintainable, testable, and scalable cross-platform applications.</p><p><strong>Ready to start your KMP journey?</strong> The complete source code for this tutorial is available on <a href="https://github.com/tomriddle25/kyklos">GitHub</a>, showcasing the foundation we’ve built together. While we’re still implementing the full feature set, the architecture and setup demonstrate production-ready KMP practices you can use as a reference for your own projects.</p><h3>Coming Next in This Series:</h3><p>📝 <strong>Part 2:</strong> “KMP Reality Check: 5 Gotchas That Almost Killed My Project”<br>📝 <strong>Part 3:</strong> “Material 3 on iOS: What Works, What Doesn’t, What’s Coming”<br>📝 <strong>Part 4:</strong> “State Management in KMP: Beyond ViewModel”<br>📝 <strong>Part 5:</strong> “Adding Comprehensive Testing to Your KMP Project”</p><p><strong>Follow me for updates on the Kyklos development journey!</strong></p><p><strong>Have questions about Kotlin Multiplatform development? Found this tutorial helpful? Share your thoughts and experiences in the comments below!</strong></p><p><strong>Tags:</strong> #KotlinMultiplatform #ComposeMultiplatform #AndroidDev #iOSDev #MobileDev #CrossPlatform #CleanArchitecture #SQLDelight</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4d3f4c6767c" width="1" height="1" alt=""><hr><p><a href="https://blog.kotlin-academy.com/building-your-first-kotlin-multiplatform-app-from-setup-to-ios-app-store-with-compose-e4d3f4c6767c">Building Your First Kotlin Multiplatform App: From Setup to iOS App Store with Compose…</a> was originally published in <a href="https://blog.kotlin-academy.com">kt.academy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose Modifiers]]></title>
            <link>https://blog.devgenius.io/jetpack-compose-modifiers-211f807de8ea?source=rss-e785afa712bc------2</link>
            <guid isPermaLink="false">https://medium.com/p/211f807de8ea</guid>
            <category><![CDATA[layout]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[modifier]]></category>
            <dc:creator><![CDATA[Faisal Ahmed]]></dc:creator>
            <pubDate>Sun, 19 Feb 2023 19:39:58 GMT</pubDate>
            <atom:updated>2023-02-24T13:01:59.649Z</atom:updated>
            <content:encoded><![CDATA[<p>Jetpack Compose is a modern UI toolkit for building Android user interfaces using a declarative programming model. It allows developers to build complex UI components with ease by simply composing smaller, reusable components. One of the key features of Jetpack Compose is its Modifier system. In this article, we will take a closer look at what modifiers are and how they work.</p><h3>What are Modifiers?</h3><p>In Jetpack Compose, a Modifier is an object that can be used to modify the layout or behavior of a UI component. Modifiers are applied to a UI component to adjust its appearance, size, position, and other attributes. A Modifier can be chained with other Modifiers to create a sequence of transformations, each building on the previous one.</p><p>A Modifier can be used to achieve various effects such as adding padding, margins, or a background color to a UI component. Modifiers can also be used to change the behavior of a component, for example, by making it clickable or adding a tooltip.</p><h3>How to Use Modifiers?</h3><p>To apply a Modifier to a UI component, you simply chain it with the component using the dot notation. For example, if you want to add some padding to a Text component, you can use the following code:</p><pre>Text(<br>    text = &quot;Hello World&quot;,<br>    modifier = Modifier.padding(16.dp)<br>)</pre><p>In the above code, we have applied a padding Modifier to the Text component with a value of 16.dp. The dp unit is a density-independent pixel that allows UI elements to scale correctly across different device screen densities.</p><p>Modifiers can also be combined to create more complex transformations. For example, to apply both a padding and a background color to a Text component, we can use the following code:</p><pre>Text(<br>    text = &quot;Hello World&quot;,<br>    modifier = Modifier<br>        .padding(16.dp)<br>        .background(Color.Red)<br>)</pre><p>In the above code, we have applied both a padding Modifier and a background color Modifier to the Text component.</p><h3>Common Modifiers</h3><p>Jetpack Compose provides a wide range of built-in Modifiers that can be used to modify the appearance and behavior of UI components. Some of the commonly used Modifiers are:</p><ul><li>padding: Adds padding to a UI component.</li><li>background: Adds a background color or drawable to a UI component.</li><li>clickable: Makes a UI component clickable.</li><li>width: Sets the width of a UI component.</li><li>height: Sets the height of a UI component.</li><li>weight: Sets the weight of a UI component within a Composable layout.</li><li>fillMaxWidth: Makes a UI component fill the available width within a Composable layout.</li><li>fillMaxHeight: Makes a UI component fill the available height within a Composable layout.</li></ul><h3>Modifier Chaining and Order</h3><p>We will almost always find ourselves chaining a bunch of modifiers while developing UI in Compose, hence it is very important to be mindful of the order in which we define our modifiers.</p><p>Reusing our example from above,</p><pre>Text(<br>    text = &quot;Hello World&quot;,<br>    modifier = Modifier<br>        .padding(16.dp)<br>        .background(Color.Red)<br>)</pre><p>We are setting the padding for the Text first and then setting the background to Color.Red which means that the modifier system will,</p><ul><li>First apply a padding of 16 dps around the text, and</li><li>Then draw a Red colored background behind the text.</li></ul><p>Result</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/650/1*lgU-X2-B99_V8qduSEylIQ.png" /></figure><p>If instead we switch the order of the modifiers ,</p><pre>Text(<br>    text = &quot;Hello World&quot;,<br>    modifier = Modifier<br>        .background(Color.Red)<br>        .padding(16.dp)<br>)</pre><p>The modifier system will,</p><ul><li>First draw a Red background behind the text, and</li><li>Then apply a padding of 16 dp in all directions.</li></ul><p>Result,</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/634/1*UG6sJDCtt1gPXywjGnsEpQ.png" /></figure><h3>Custom Modifiers</h3><p>Developers can also create their own custom Modifiers to achieve specific layout or behavioral effects. To create a custom Modifier, you need to define a function that takes in a Modifier object and returns a new Modifier object with the desired transformation applied. For example, the following code defines a custom Modifier that adds a border to a UI component:</p><pre>fun Modifier.border(<br>    width: Dp = 1.dp,<br>    color: Color = Color.Black<br>): Modifier = this.then(<br>    BorderStroke(width, color)<br>)</pre><p>In the above code, we have defined a border Modifier that takes in two parameters, width and color. The Modifier applies a BorderStroke transformation to the UI component with the given width and color.</p><h3>Conclusion</h3><p>Jetpack Compose Modifier system is a powerful tool that allows developers to create complex UI components with ease. Modifiers provide a declarative way to define layout and behavioral transformations, making it easier to reason about the UI code, but it is important to be mindful of things like modifier chaining and ordering while writing UI.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=211f807de8ea" width="1" height="1" alt=""><hr><p><a href="https://blog.devgenius.io/jetpack-compose-modifiers-211f807de8ea">Jetpack Compose Modifiers</a> was originally published in <a href="https://blog.devgenius.io">Dev Genius</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>