Flutter to Native Code Migration: Data | 3/5
Data Handling and Mapping: Use Case being Supabase

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 (
Intid in DTO mapped toStringid 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.



