Skip to main content

Command Palette

Search for a command to run...

Flutter to Native Code Migration: Setup | 2/5

Planning Your Migration: Architecture and Project Setup

Updated
6 min read
Flutter to Native Code Migration: Setup | 2/5
S

Software engineer and a Technical Writer, Best at Flutter mobile app development, full stack development with Mern. Other areas are like Android, Kotlin, .Net and Qt

Introduction

Migrating a Flutter app to native Android (Kotlin + Jetpack Compose) and iOS (Swift + SwiftUI) with MongoDB with custom backend as the backend can unlock native capabilities while retaining your scalable backend. Planning your architecture and project setup properly ensures a smooth transition.


Project Setup

a) Android

Start by creating a new Android project in Android Studio. Once your project is initialized, follow these steps to configure dependancies:

  1. In your root/build.gradle.kts, ensure you have:
plugins {
    alias(libs.plugins.android.application) apply false     // Android application plugin
    alias(libs.plugins.kotlin.android) apply false      // Kotlin support for Android
    alias(libs.plugins.kotlin.compose) apply false      // Jetpack Compose compiler plugin
    alias(libs.plugins.dagger.hilt) apply false     // Hilt for dependency injection
    alias(libs.plugins.devtools.ksp) apply false     // Kotlin Symbol Processing (used by Room and Hilt packages)
}
  1. In your app/build.gradle.kts, ensure you have:
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.dagger.hilt)
    alias(libs.plugins.devtools.ksp)
    kotlin("plugin.serialization") version "2.1.21"
}

at the top and in the depencies you may want to organize them this way

dependencies {
    // Core AndroidX & Lifecycle
    implementation(libs.androidx.core.ktx)      // Kotlin extensions for core Android APIs
    implementation(libs.androidx.lifecycle.runtime.ktx)      // Lifecycle-aware components
    implementation(libs.androidx.core.splashscreen)      // Splash screen API

    // Jetpack Compose - BOM
    implementation(platform(libs.androidx.compose.bom))      // Compose Bill of Materials (BOM)

    // Jetpack Compose - Core UI
    implementation(libs.androidx.activity.compose)      // Activity support for Compose
    implementation(libs.androidx.ui)      // Core Compose UI elements
    implementation(libs.androidx.ui.graphics)      // Compose graphics primitives

    // Jetpack Compose - Material Design
    implementation(libs.androidx.material3)      // Material 3 UI components
    implementation(libs.androidx.compose.material)      // Material 2 components (legacy support)
    implementation(libs.androidx.icons.extended)      // Extended material icons

    // Jetpack Compose - Navigation & State
    implementation(libs.compose.navigation)      // Navigation support in Compose
    implementation(libs.compose.hilt.navigation)      // Hilt + Navigation integration
    implementation(libs.androidx.compose.livedata)      // LiveData support in Compose

    // Jetpack Compose - Tooling & Preview
    implementation(libs.androidx.ui.tooling.preview)      // Compose UI preview support
    debugImplementation(libs.androidx.ui.tooling)      // Compose UI tools (debug only)
    debugImplementation(libs.androidx.ui.test.manifest)      // Manifest for Compose UI tests (debug)

    // Navigation (Non-Compose)
    implementation(libs.androidx.navigation)      // AndroidX Navigation components

    // Room Database
    implementation(libs.androidx.room.runtime)      // Room runtime
    ksp(libs.androidx.room.compiler)      // Room annotation processor (KSP)
    annotationProcessor(libs.androidx.room.compiler)      // Annotation processor (for fallback/tests)

    // Hilt for Dependency Injection
    implementation(libs.hilt.android)      // Hilt core
    ksp(libs.hilt.compiler)      // Hilt code generation
    kspAndroidTest(libs.hilt.android.compiler)      // Hilt codegen for instrumentation tests

    // Networking (Retrofit + OkHttp)
    implementation(libs.squareup.retrofit)      // Retrofit for networking
    implementation(libs.squareup.retrofit.gson)      // Gson converter for Retrofit
    implementation(libs.squareup.okhttp3.logging)      // OkHttp logging interceptor

    // Testing - Unit Tests
    testImplementation(libs.junit)      // JUnit for unit testing

    // Testing - Android Instrumentation Tests
    androidTestImplementation(libs.androidx.junit)      // AndroidX JUnit extensions
    androidTestImplementation(libs.androidx.espresso.core)      // Espresso UI test framework
    androidTestImplementation(platform(libs.androidx.compose.bom))      // Compose BOM for tests
    androidTestImplementation(libs.androidx.ui.test.junit4)      // Compose JUnit4 test support
}
  1. Now sync your project in readiness for the next setup of your project

b) iOS

Start by creating a new iOS project in Xcode using Swift + SwiftUI. Once your project is initialized, follow these steps to configure dependencies:

  1. Add Dependencies via the Swift Package Manager by going to:
    File → Add Packages

  2. Add Swinject by entering:

     https://github.com/Swinject/Swinject.git
    

    and select the latest stable version. All other packages are added that way.

Project Architecture

After setting up dependencies and basic wiring, it is important to have a clean architecture that can scale and remain testable across Android, and iOS.

I worked maintaining similar layering that was already using in Flutter:

Maintaining similar layering across Android and iOS

  • Core – Dependency injection, constants, environment configurations, helpers, utilities.

  • Data – Data models, data sources (local and remote), mappers, DTOs.

  • Domain – Entities, repositories (interfaces and implementations).

  • Presentation – Screens/views, view models/BLoCs, UI components, themes.

This was my flutter setup

lib
├── core
│   ├── di
│       ├── injectible.dart
│   ├── utils
├── data
│   ├── models
│       ├── song.dart
│   ├── sources
│       ├── local
│           ├── daos (Domain Access Objects)
│           ├── app_database.dart
│       ├── remote
├── domain
│   ├── repository
│       ├── song_repository.dart
├── presentation
│   ├── blocs
│       ├── selection
│       ├── home
│   ├── screens
│       ├── selection
│       ├── home
│   ├── widgets
├── app.dart (boostrapper)
└── main.dart (app launcher/initializer)
  • Uses BLoC for state management.

  • Local data (Floor) and remote data (backend client).

  • Dependency injection via Injectable and GetIt.

  • Keeps UI logic in screens, while reusable pieces of code in widgets.

  • Environment and constants in core/utils/env.

a) Android (Kotlin, Jetpack Compose)

app
├── core
│   ├── di
│   ├── ├── AppModule.kt
│   ├── ├── NetworkModule.kt
│   ├── utils
├── data
│   ├── models
│       ├── Song.kt
│   ├── samples
│   ├── sources
│       ├── local
│           ├── AppDaos.kt (Data Access Objects)
│           ├── AppDatabase.kt
│       ├── remote
│           ├── ApiService.kt
├── domain
│   ├── entity
│       ├── UiState.kt
│   ├── repository
│       ├── SongRepository.kt
├── presentation
│   ├── components
│   ├── screens
│       ├── selection
│       ├── home
│   ├── theme
│   ├── viewmodels
│       ├── SelectionViewModel.kt
│       ├── HomeViewModel.kt
├── MainActivity.kt
└── SongLibApp.kt
  • Uses ViewModel + StateFlow for state management.

  • Dependency Injection via Koin/Hilt.

  • Room for local caching, Retrofit for remote.

  • UI uses Jetpack Compose with components for reusable UI blocks.

b) iOS (SwiftUI)

MyNewApp
├── Core
│   ├── Di
│       ├── DependencyMap.swift
│       ├── DiContainer.swift
│   ├── Utils
├── Data
│   ├── Models
│       ├── Song.swift
│   ├── Samples
│   ├── Sources
│       ├── Local
│           ├── CoreDataManager.swift
│           ├── SongDataManager.swift
│           ├── MyNewApp.xcdatamodelId
│       ├── Remote
│           ├── ApiService.swift
│           ├── EndPoint.swift
├── Domain
│   ├── Entity
│   ├── Repository
│       ├── SongRepository.swift
├── Presentation
│   ├── Components
│   ├── Views
│       ├── Selection
│       ├── Home
│   ├── Theme
│   ├── ViewModels
│       ├── SelectionViewModel.swift
│       ├── HomeViewModel.swift
├── Assets.xcassets
├── ContentView.swift
└── SongLibApp.swift
  • Uses ObservableObject + @Published or Combine for state management.

  • Dependency injection via lightweight DependencyMap and DiContainer.

  • Uses Core Data for local, ApiService.swift for remote.

  • SwiftUI structures with Views and reusable Components.

  • DTO and Core Data entity mapping handled explicitly in Mappers.

Strengths in a clean structure:

  1. Consistent Layering: Core, Data, Domain, Presentation clearly separate concerns hence its easy to understand and maintain.

  2. Alignment Across Platforms: The same mental model applies for Flutter (BLoC), Android (ViewModel + StateFlow), and iOS (ObservableObject/Combine). DTOs and entity mappers handled explicitly, ensuring testability and separation of network vs. app models.

  3. Testability & Modularity: Dependency Injection handled cleanly (Injectable, Koin/Hilt, DependencyMap/Container). Local storage handled (Floor, Room, Core Data) while ApiService remains the source of truth.

  4. UI Organization: Presentation is split into screens/views, view models/BLoCs, and reusable components/widgets, keeping screens clean.

  5. Bootstrap & Entry Separation: main.dart/MyNewApp.kt/MyNewApp.swift as app launchers. app.dart/ContentView.swift as bootstrap entry, further isolating setup.

  6. Mappers: You explicitly handle DTO ↔ Entity ↔ Local mappings in dedicated files across all platforms. This prevents leaky abstractions and future issues when the API or local storage changes.

  7. Sample Data: The samples folder is clean for seeding/testing, consider documenting its purpose in your repo for contributors.

  8. Future Expansion: Consider having a usecases subfolder inside domain if you add business logic later, but for a small iOS project.


In the next article, we will cover Data Handling and Mapping strategies, including DTOs, entities, and why mapping layers matter when migrating.

Flutter to Native

Part 2 of 5

3 months ago I challenged myself to rewrite my 2 Flutter apps into native: Android and iOS using Jetpack Compose and SwiftUI respectively. Well both apps are live in both Google Playstore and Apple Store! This is my developer's journey.

Up next

Flutter to Native Code Migration: Data | 3/5

Data Handling and Mapping: Use Case being Supabase