Skip to main content

Command Palette

Search for a command to run...

Flutter to Native Code Migration: Data | 3/5

Data Handling and Mapping: Use Case being Supabase

Updated
3 min read
Flutter to Native Code Migration: Data | 3/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

I learned the hard way that migrating my Flutter app to native platforms demands clean, explicit data handling. In Flutter, I relied on the dynamic fromJson approach, but when I migrated SwahiLib (which uses Supabase as a backend), I realized that Android and iOS required explicit DTO-to-entity mapping for type safety, testability, and maintainability.

For my other app, SongLib (which uses MongoDB with a custom backend), the Android setup worked fine with minimal mapping. However, on iOS, I still needed to map data explicitly to and from CoreDataManager whenever I wanted to sync local data, reinforcing the need for clear data handling even when using different backend services.

Why Use DTOs and Mappers?

  • DTOs (Data Transfer Objects): Mirror the structure returned by the backend service e.g Supabase/Postgres.

  • Domain Entities: Clean app-facing models used in your presentation/business layers.

  • Mappers: Convert DTOs → Entities to isolate your app logic from backend changes.

In Flutter, the supabase_flutter SDK returns data as Map<String, dynamic>, which you can directly pass into your model's fromJson constructor:

Fruit.fromJson(item);

No explicit mapping layer is needed since Dart’s dynamic typing and JSON decoding handle shape alignment directly.

However, in Android (Kotlin) and iOS (Swift), it is best practice to separate the raw DTOs fetched from Supabase from your domain models. This separation ensures:

  • Type safety across layers.

  • Clean handling if the Supabase schema changes (without breaking your app immediately).

  • Flexibility to add computed properties or transformations in your domain models without affecting your DTOs.

a) Android (Kotlin)

FruitDTO: represents exactly what Supabase returns:

@Serializable
data class FruitDto(
    val id: Int = 0,
    val title: String? = null,
    val description: String? = null,
    val color: String? = null,
    val createdAt: String? = null,
    val updatedAt: String? = null,
)

Fruit (Model): Your clean, app-facing entity:

@Keep
data class Fruit(
    val id: String? = null,
    val title: String? = null,
    val description: String? = null,
    val color: String? = null,
    val createdAt: String? = null,
    val updatedAt: String? = null,
)

MapDtoToEntity (Mapper): helps to map the DTO to your domain model to keep your presentation layer and business logic decoupled from backend structure:

MapDtoToEntity {
    fun mapToEntity(entity: FruitDto): Fruit {
        return Fruit(
            id = entity.id,
            title = entity.title,
            description = entity.description,
            color = entity.color,
            createdAt = entity.createdAt,
            updatedAt = entity.updatedAt,
        )
    }
}
  • Ensures consistent types (Int id in DTO mapped to String id in domain if needed).

  • Allows DTO to evolve without tightly coupling your app logic.

b) iOS (Swift)

FruitDTO: Matches the structure returned by Supabase:

struct FruitDTO: Codable {
    let id: String?
    let title: String?
    let description: String?
    let color: String?
    let createdAt: String?
    let updatedAt: String?
}

Fruit Model: Used in your SwiftUI views and logic

struct Fruit: Identifiable, Codable {
    let id: Int
    let title: String
    let description: String
    let color: String
    let createdAt: String
    let updatedAt: String
}

MapDtoToEntity Mapper: You map from FruitDTO to Fruit, handling defaults and type transformations:

struct MapDtoToEntity {
    static func mapToEntity(_ dto: FruitDTO) -> Fruit {
        Fruit(
            id: Int(dto.id ?? "0") ?? "",
            title: dto.title ?? "",
            description: dto.description ?? "",
            color: dto.color ?? "",
            createdAt: dto.createdAt ?? "",
            updatedAt: dto.updatedAt ?? ""
        )
    }
}

✅ Allows you to handle missing or optional fields gracefully.
✅ Keeps SwiftUI clean by providing non-optional properties for use in views.

Benefits of Explicit Mapping:

  • Decouples my app’s structure from the backend schema.

  • Handles optional/null safely.

  • Enables computed properties and transformation logic.

  • Makes your codebase scalable and testable.


In the next article, I will cover Supabase setup and implementation across Android, and iOS while comparing it against my Flutter setup, including credential handling, client initialization, and practical fetching examples.

Flutter to Native

Part 3 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: Supabase | 4/5

The Supabase Setup and Implementation