AL+

Limba: RO (română) EN (English)

Scrieți-ne: paul@alplustech.com

Kotlin Multiplatform sau Flutter: aceeași logică pe iOS și Android, interfață nativă

Google a susținut Kotlin Multiplatform pentru logica de business comună. Când merită să partajați regulile între iOS și Android și să păstrați interfața nativă.

Read this article in English

Când o firmă vrea o aplicație pe iPhone și pe Android, întrebarea care apare repede este dacă se poate scrie o singură dată, cu același cod. Comparațiile dintre Kotlin Multiplatform, Flutter și React Native răspund de obicei cu tabele de funcționalități. Întrebarea mai utilă este ce cod merită să fie comun. Pentru produse pe ambele platforme, cu reguli importante în aplicație și o echipă care poate întreține Kotlin și ambele interfețe native, recomand partajarea logicii de business: regulile de preț, validările, modelele de date, sincronizarea și comportamentul fără conexiune. Interfața e mai bine să rămână nativă. Logica scrisă de două ori produce erori care costă bani și se văd greu. O interfață comună produce compromisuri pe care utilizatorii fiecărei platforme le observă. Sprijinul Google din mai 2024 întărește argumentul acestei împărțiri. Fiecare echipă trebuie însă să evalueze costurile cu oamenii, compilarea și comunicarea dintre limbaje.

Ce a susținut Google, de fapt

La Google I/O, pe 14 mai 2024, echipa Android a anunțat suportul oficial pentru Kotlin Multiplatform (KMP). Anunțul era mai precis decât titlurile care au urmat. Google a spus că se concentrează pe partajarea logicii de business, adică a părților cele mai puțin legate de interfață, pentru că acolo văzuse cel mai mare câștig pentru dezvoltatorii Android: nu mai trebuie să întrețină copii duplicate ale aceluiași cod.

Anunțul venea cu dovezi, nu doar cu o declarație de presă. Aplicația Google Docs pentru Android, iOS și web folosea KMP pentru logica de business comună, iar echipa Workspace numea KMP direcția arhitecturii sale pentru mai multe platforme. Mai multe biblioteci Jetpack primiseră suport KMP: Annotations, Collections și DataStore în versiuni stabile, Room, Lifecycle și ViewModels în alpha. Android Gradle Plugin a primit suport oficial pentru ținte KMP, iar inginerii Android au raportat o îmbunătățire de 18% a performanței la rulare în benchmarkurile compilatorului Kotlin/Native.

Tehnologia nu era nouă. JetBrains declarase Kotlin Multiplatform stabil în Kotlin 1.9.20 în noiembrie 2023. Atunci, Compose Multiplatform, varianta care permite și o interfață comună, era încă în alpha pe iOS. Ce s-a schimbat în mai 2024 a fost riscul. O echipă care alegea KMP nu mai depindea doar de un singur furnizor de unelte. Proprietarul platformei Android își pusese în spatele abordării propria aplicație importantă și propriile biblioteci, și le pusese anume în spatele stratului de logică.

Unde costă logica duplicată

Gândiți-vă la ce dublează, de fapt, două aplicații native fără cod comun. Fiecare regulă despre bani, eligibilitate, date calendaristice și stări se scrie o dată în Kotlin și o dată în Swift, de oameni diferiți, de obicei în momente diferite. Un exemplu mic din genul de cod care își are locul într-un modul comun:

Exemplul ilustrează doar rotunjirea, nu o funcție completă de calcul al prețului. Presupune date deja validate: prețuri și cantități nenegative, fiecare produs și suma lor în limitele tipului Long, iar gross * discountPercent tot în aceste limite. Codul verifică doar intervalul reducerii. În producție, aceste limite trebuie impuse înainte de apel.

// commonMain: compiled for Android and for iOS
data class OrderLine(val unitPriceCents: Long, val quantity: Int)

fun orderTotalCents(lines: List<OrderLine>, discountPercent: Int): Long {
    require(discountPercent in 0..100) { "Discount must be between 0 and 100" }
    val gross = lines.sumOf { it.unitPriceCents * it.quantity }
    return gross - gross * discountPercent / 100
}

Împărțirea întreagă de la final aruncă restul, așa că reducerea se rotunjește în jos, iar clientul plătește fracțiunea de ban în plus. Aceasta e o decizie de business. Scrisă de două ori, se ia de două ori, și nimic nu garantează că versiunea din Swift o ia la fel. Adăugați reducerile cumulate, TVA-ul, fusurile orare, regulile de reîncercare pentru operațiile făcute fără conexiune și rezolvarea conflictelor când două dispozitive modifică aceeași înregistrare. Diferențele acestea nu blochează aplicația. Apar ca un tichet la suport în care un client spune că aplicația de iPhone i-a calculat altă sumă sau ca o eroare de sincronizare care apare doar când un utilizator de Android și unul de iOS folosesc același cont. Sunt scumpe pentru că le descoperă târziu clienții.

Un modul comun dă acestei logici o singură implementare și un singur set de teste. Acesta este cel mai puternic argument pentru KMP și ține mai mult de risc decât de viteză.

Unde costă o interfață comună pe iOS

La interfață lucrurile stau invers. Diferențele dintre platforme nu sunt erori de eliminat. Sunt ce așteaptă utilizatorii: gesturile de navigare, selecția textului, ferestrele sistemului, accesibilitatea și, tot mai mult, aspectul sistemului însuși.

Cel mai clar test a venit după perioada la care se uită acest articol. În iunie 2025, Apple a prezentat Liquid Glass, un nou material de design în iOS 26 și în celelalte sisteme ale sale, pe care l-a numit cea mai amplă actualizare de design software din istoria companiei. Pentru aplicațiile construite cu SwiftUI și UIKit, Apple a livrat API-uri și controale actualizate pentru noul design. Flutter funcționează altfel: potrivit prezentării arhitecturii, are propriile implementări pentru fiecare control de interfață și nu le folosește pe cele ale sistemului. A doua zi după anunțul Apple, echipa Flutter a scris în discuția despre suportul pentru Liquid Glass că nu dezvoltă deocamdată noile elemente de design Apple în biblioteca Cupertino. În iulie 2025 a adăugat că biblioteca Cupertino se mută într-un pachet separat, unde va continua lucrul. În noiembrie 2026, discuția era încă deschisă.

Nu e o critică la adresa ingineriei din Flutter. Desenarea fiecărui control e un compromis asumat, care aduce consecvență între platforme și control asupra randării. Costul este că, atunci când platforma se schimbă, aplicația așteaptă framework-ul. React Native stă la mijloc: documentația spune că, la rulare, creează elementele de interfață native corespunzătoare pe Android și pe iOS, deci e mai puțin expus la problema aceasta. O aplicație KMP cu interfață în SwiftUI e în aceeași situație ca orice aplicație nativă, pentru că interfața este a Apple.

Ce s-a schimbat în doi ani

După doi ani, lucrurile s-au mișcat în ambele direcții.

Argumentele pentru a partaja mai mult s-au întărit. JetBrains a declarat Compose Multiplatform pentru iOS stabil în mai 2025, odată cu versiunea 1.8.0. KMP nu mai e doar un strat de logică: notele de lansare descriu integrarea Compose într-o aplicație iOS existentă și includerea de componente native într-un ecran Compose, așa că o echipă poate partaja unele ecrane și le poate păstra native pe altele. Flexibilitatea e utilă. Readuce însă întrebarea din 2024 în altă formă. Aceleași note de lansare enumeră derularea care urmează fizica nativă din iOS și selecția nativă a textului printre detaliile pe care JetBrains a trebuit să le pună la punct, adică exact munca de recuperare pe care o cere orice interfață comună pe iOS.

Partea de iOS a rămas punctul slab al KMP. Potrivit documentației Kotlin, Kotlin/Native comunică cu Swift indirect, prin Objective-C. Funcțiile suspend ajung în Swift ca funcții cu completion handler sau ca funcții async. Chiar anunțul Google din 2024 trimitea la SKIE, unealta Touchlab care face tipurile moderne și corutinele utilizabile din Swift. Calea directă, Swift export, este descrisă tot ca Alpha. În practică, un dezvoltator iOS dintr-o echipă KMP lucrează cu un API generat, pe care nu l-a proiectat el. E un cost real și cade pe platforma ai cărei utilizatori sunt cei mai pretențioși cu finisajul.

Cel mai puternic argument contra

Cel mai bun argument împotriva acestei împărțiri este costul. O interfață nativă înseamnă două coduri de interfață, două seturi de competențe și două runde de testare a interfeței. Pentru o echipă mică, cu un singur designer, care face o aplicație internă pentru angajați, e greu de justificat. Angajații folosesc aplicația pentru că le-o cere munca, nu pentru că arată ca iOS. Un singur cod în Flutter sau în React Native le dă o aplicație funcțională pe ambele platforme, făcută de o singură echipă, iar dacă interfața rămâne cu un an în urma unui redesign al platformei, nu pleacă nimeni.

E o decizie corectă, iar concluzia de aici nu o acoperă. Concluzia nu se aplică nici în alte trei situații:

  • Aplicația e doar pentru iOS sau doar pentru Android. Nu există nimic de partajat între platforme, iar KMP adaugă un strat de build fără folos. Multe produse ar trebui să pornească pe o singură platformă, o decizie separată, discutată în articolul despre aplicația web și aplicația mobilă (în engleză).
  • Regulile stau pe server. Dacă prețurile, permisiunile și validările le decide backendul, iar aplicațiile doar afișează rezultatele, duplicarea e mică. Mutarea logicii pe server e adesea un răspuns mai bun decât partajarea ei în aplicație.
  • Echipa nu are experiență cu Kotlin. O echipă de iOS care adoptă KMP își asumă Gradle, încă un limbaj și încă un debugger. Fără cineva care să răspundă de modulul comun, acesta devine partea de care nu vrea nimeni să se atingă.

Concluzia

Pentru un produs folosit de clienți pe ambele platforme, cu reguli importante pentru business, partajați logica și păstrați interfața nativă. Anunțul Google din mai 2024 nu a făcut KMP perfect. A adus sprijinul proprietarului platformei și un exemplu de utilizare în producție la scară mare. Pentru echipa și produsul descrise mai sus, acestea susțin recomandarea mea; nu dovedesc un risc mic pentru orice proiect. Interfața este locul unde utilizatorii judecă produsul, iar platformele o schimbă mereu.

Serviciul nostru de aplicații pentru iPhone și iPad acoperă produse pentru aceste dispozitive. Reutilizarea codului într-o viitoare aplicație Android este o decizie de arhitectură pentru fiecare proiect: depinde de locul regulilor, de competențele echipei și de funcționalitățile agreate. Pornirea doar pe iPhone nu cere un modul Kotlin comun.

Surse