Kotlin Multiplatform è il futuro dello sviluppo multipiattaforma? Suggerimenti su come iniziare

Pubblicato: 2021-01-29

Al giorno d'oggi, possiamo osservare una tendenza nello sviluppo mobile per rilasciare app più velocemente. Ci sono stati molti tentativi di ridurre i tempi di sviluppo condividendo parti di codice comuni tra piattaforme diverse come Android e iOS. Alcune soluzioni hanno già guadagnato popolarità, mentre altre sono ancora in fase di sviluppo. Oggi vorrei parlare di uno degli approcci più recenti del secondo gruppo: Kotlin Multiplatform Mobile (KMM in breve).

Cos'è Kotlin Multipiattaforma Mobile?

KMM è un SDK che mira principalmente a condividere la logica aziendale tra le piattaforme , la parte che nella maggior parte dei casi deve comunque essere la stessa. Ciò si ottiene grazie a un insieme di più compilatori per un modulo condiviso. Ad esempio, il target Android utilizza una variante Kotlin/JVM e per iOS ce n'è una Kotlin/Native. Un modulo condiviso può quindi essere aggiunto ai tipici progetti di app native e gli sviluppatori responsabili dell'interfaccia utente possono concentrarsi sulla fornitura della migliore esperienza per gli utenti in un ambiente a loro familiare: Android Studio per Android e Xcode per iOS.

Multipiattaforma Kotlin vs Flutter

Attualmente, una delle soluzioni più popolari per lo sviluppo di app multipiattaforma è Flutter. Si concentra sulla regola "scrivi un'app ed eseguila ovunque", che funziona, ma solo per app semplici. In scenari di casi reali, gli sviluppatori spesso devono comunque scrivere codice nativo per ciascuna piattaforma per colmare le lacune , ad esempio quando mancano alcuni plugin. Con questo approccio, l'app ha lo stesso aspetto su piattaforme diverse, il che a volte è desiderabile, ma in alcuni casi può violare specifiche linee guida di progettazione.

Icona dei servizi di sviluppo multipiattaforma

Pronto a creare la tua app?

Scegli Flutter

Sebbene possano sembrare simili, Kotlin Multiplatform non è una soluzione multipiattaforma: non cerca di reinventare la ruota. Gli sviluppatori possono comunque utilizzare gli strumenti che conoscono e apprezzano. Semplifica semplicemente il processo di riutilizzo di parti di codice che in precedenza avrebbero dovuto essere scritte più volte, come l'esecuzione di richieste di rete, l'archiviazione di dati e altre logiche aziendali.

Pro e contro di Kotlin Multiplatform

Pro di KMM :

  • L'app sviluppata è nativa al 100% per ciascuna piattaforma : è facile da integrare con il codice attualmente utilizzato e le librerie di terze parti
  • Facile da usare : quasi tutti gli sviluppatori Android utilizzano già Kotlin, quindi sono necessarie pochissime conoscenze aggiuntive per iniziare
  • L' interfaccia utente può essere suddivisa per ciascuna piattaforma di destinazione : l'app si sentirà coerente con un determinato ecosistema
  • La logica condivisa consente agli sviluppatori di aggiungere nuove funzionalità e correggere bug su entrambi i sistemi operativi contemporaneamente

Contro di KMM :

  • Molti componenti sono ancora in fase Alpha/Beta e potenzialmente possono essere instabili o cambiare in futuro

Quali aziende utilizzano KMM?

Secondo il sito ufficiale, le aziende stanno guadagnando sempre più interesse per questa tecnologia e l'elenco è sempre più lungo. Tra questi, ci sono marchi famosi come Autodesk, VMWare, Netflix o Yandex.

Come iniziare con Kotlin Multiplatform?

Il posto migliore dove immergersi per informazioni approfondite è la guida ufficiale, ma in questo articolo vorrei mostrare un esempio abbastanza semplice, ma più interessante di un semplice "Hello World", che sarebbe il recupero e la visualizzazione di app l'ultimo fumetto di Randall Munroe (con licenza CC BY-NC 2.5) con il titolo dell'API xkcd.com.

Caratteristiche da coprire:

  • Configurazione del progetto
  • Networking nel modulo condiviso
  • Interfaccia utente semplice sia per Android che per iOS

Nota: volevo che questo esempio fosse facile da leggere sia per gli sviluppatori Android che iOS, quindi in alcuni punti ho omesso intenzionalmente alcune buone pratiche specifiche della piattaforma solo per chiarire cosa sta succedendo

Configurazione del progetto

Innanzitutto, assicurati di avere le ultime versioni di Android Studio e Xcode installate perché entrambe saranno necessarie per la creazione di questo progetto. Quindi, in Android Studio, installa il plug-in KMM. Questo plugin semplifica molte cose: per creare un nuovo progetto, fai semplicemente clic su Crea nuovo progetto e seleziona Applicazione KMM.

Crea un nuovo progetto Kotlin Multiplatform Mobile

Dopo aver creato il progetto, vai al file build.gradle.kts nella directory condivisa . Qui devi specificare tutte le dipendenze richieste. In questo esempio, useremo ktor per il livello di rete, kotlinx.serialization per analizzare le risposte json dal backend e le coroutine kotlin per fare tutto in modo asincrono.

Per semplicità, di seguito fornisco elenchi che mostrano tutte le dipendenze che devono essere aggiunte a quelle già presenti. Quando aggiungi dipendenze, sincronizza semplicemente il progetto (verrà visualizzato un messaggio). Innanzitutto, aggiungi il plug-in di serializzazione alla sezione plug-in.

 plugin {
   kotlin("plugin.serialization") versione "1.4.0"
}

Quindi aggiungi le dipendenze.

 sourceSets {
   val commonMain ottenendo {
       dipendenze {
           implementazione("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
           implementazione("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt-2")

           implementazione("io.ktor:ktor-client-core:1.4.1")
           implementazione("io.ktor:ktor-client-json:1.4.1")
           implementazione("io.ktor:ktor-client-serialization:1.4.1")
       }
   }
   val androidMain ottenendo {
       dipendenze {
           implementazione("io.ktor:ktor-client-android:1.4.1")
       }
   }
   val iosMain ottenendo {
       dipendenze {
           implementazione("io.ktor:ktor-client-ios:1.4.1")
       }
   }
}

Vale la pena ricordare che al momento della stesura di questo articolo, ci sono alcuni problemi con la versione stabile della libreria coroutines su iOS – ecco perché la versione utilizzata ha il suffisso native-mt-2 (che sta per native multithreading). Puoi controllare lo stato attuale di questo problema qui.

Networking nel modulo condiviso

Innanzitutto, abbiamo bisogno di una classe che rappresenti la risposta : quei campi sono presenti in json restituito dal back-end.

 import kotlinx.serialization.Serializable

@Serializzabile
classe di dati XkcdResponse(
   val img: stringa,
   val title: String,
   val giorno: Int,
   mese val: Int,
   anno val: Int,
)

Successivamente abbiamo bisogno di creare una classe che rappresenti l'API con un client HTTP . Nel caso in cui non abbiamo fornito tutti i campi presenti in json, possiamo utilizzare la proprietà ignoreUnknownKeys in modo che il serializzatore possa ignorare quelli mancanti. Questo esempio ha un solo endpoint rappresentato da una funzione sospesa. Questo modificatore indica al compilatore che questa funzione è asincrona. Lo descriverò di più con il codice specifico della piattaforma.

 importa io.ktor.client.*
importa io.ktor.client.features.json.*
importa io.ktor.client.features.json.serializer.*
importa io.ktor.client.request.*

classe XkcdApi {
   private val baseUrl = "https://xkcd.com"

   valore privato httpClient = HttpClient() {
       install(JsonFeature) {
           serializzatore = KotlinxSerializer(
               kotlinx.serialization.json.Json {
                   ignoreUnknownKeys = true
               }
           )
       }
   }

   sospendi divertimento fetchLatestComic() =
       httpClient.get<XkcdResponse>("$baseUrl/info.0.json")

}

Quando il nostro livello di rete è pronto, possiamo passare al livello di dominio e creare una classe che rappresenta il modello locale dei dati. In questo esempio, ho saltato altri campi e ho lasciato solo il titolo del fumetto e l'URL dell'immagine.

 classe di dati ComicModel(
   val imageUrl: stringa,
   titolo val: String
)

L'ultima parte di questo livello consiste nel creare un caso d'uso che attiverà una richiesta di rete e quindi mapperà la risposta al modello locale.

 classe GetLatestComicUseCase (val privato xkcdApi: XkcdApi) {
   sospendi fun run() = xkcdApi.fetchLatestComic()
       .let { ComicModel(it.img, it.title) }
}

Semplice interfaccia utente per Android

È ora di passare alla directory androidApp : è qui che viene archiviata l'app Android nativa. Innanzitutto, dobbiamo aggiungere alcune dipendenze specifiche per Android all'altro file build.gradle.kts che si trova qui. Anche in questo caso, l'elenco seguente mostra solo le dipendenze che dovrebbero essere aggiunte a quelle già presenti. Questa app utilizzerà l'architettura Model-View-ViewModel (le prime due righe) e Glide per caricare un'immagine comica dall'URL restituito (le seconde due righe)

 dipendenze {
   implementazione("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
   implementazione("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
   implementazione ("com.github.bumptech.glide:glide:4.11.0")
   annotationProcessor("com.github.bumptech.glide:compiler:4.11.0")
}

Per impostazione predefinita, un progetto appena creato deve contenere MainActivity e il relativo file di layout activity_main.xml . Aggiungiamo alcune visualizzazioni: una TextView per il titolo e una ImageView per il fumetto stesso.

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
   androide:
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravità="centro"
   android:orientamento="verticale">

   <Vista testo
       androide:
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   <Vista immagine
       androide:
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</Layout lineare>

Quindi avremo bisogno di una rappresentazione dello stato dell'app : può essere il caricamento di un nuovo fumetto, la sua visualizzazione o potremmo riscontrare un errore durante il caricamento.

 classe sigillata Stato {
   caricamento dell'oggetto : State()
   class Success(val result: ComicModel) : State()
   Errore oggetto: State()
}

Ora aggiungiamo un ViewModel minimo usando la business logic precedentemente creata . Tutte le classi possono essere importate. MutableLiveData è un campo osservabile: la vista osserverà le modifiche e si aggiornerà di conseguenza. viewModelScope è un ambito coroutine legato al ciclo di vita di viewmodel: nel caso in cui l'app venga chiusa, annullerà automaticamente le attività in sospeso.

 class MainViewModel : ViewModel() {
   privato val getLatestComicUseCase = GetLatestComicUseCase(XkcdApi())
   val comic = MutableLiveData<Stato<ComicModel>>()

   divertimento fetchComic() {
       viewModelScope.launch {
           comic.value = State.Loading()
           runCatching { getLatestComicUseCase.run() }
               .onSuccess { comic.value = State.Success(it) }
               .onFailure { comic.value = State.Error() }
       }
   }
}

Un'ultima cosa: MainActivity per cablare tutto.

 classe MainActivity: AppCompatActivity (R.layout.activity_main) {
   private val viewModel: MainViewModel di lazy {
      ViewModelProvider(this).get(MainViewModel::class.java)
   }

   ignora fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      viewModel.comic.observe(questo) {
         quando esso) {
            è stato.Loading -> {
               findViewById<TextView>(R.id.titleLabel).text = "Caricamento in corso"
            }
            è State.Success -> {
               findViewById<TextView>(R.id.titleLabel).text = it.result.title
               Scivola.con(questo)
                  .load(it.result.img)
                  .in(findViewById(R.id.image))
            }
            è Stato.Errore -> {
               findViewById<TextView>(R.id.titleLabel).text = "Errore"
            }
         }
      }
      viewModel.fetchComic()
   }
}

Ecco fatto, l'app per Android è pronta!

Un'applicazione Android sviluppata con KMM

Interfaccia utente semplice per iOS

Tutto quanto sopra è stato fatto in Android Studio, quindi per questa parte passiamo a Xcode per renderlo più conveniente. Per fare ciò basta aprire Xcode e selezionare la directory iosApp : contiene un progetto Xcode preconfigurato. Per impostazione predefinita, questo progetto utilizza SwiftUI per la GUI, quindi atteniamoci ad esso per semplicità.

La prima cosa da fare è creare una logica di base per recuperare i dati dei fumetti. Proprio come prima, abbiamo bisogno di qualcosa che rappresenti lo stato.

 enum stato {
    caricamento del caso
    caso di successo (ComicModel)
    errore del caso
}

Quindi, prepariamo ancora una volta un ViewModel

 class ViewModel: ObservableObject {
    lascia getLatestComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi())
        
    @Published var comic = State.loading
        
    dentro() {
        self.comic = .caricamento
        getLatestComicUseCase.run {fetchedComic, errore in
            if fetchedComic != nil {
                self.comic = .success(fetchedComic!)
            } altro {
                self.comic = .errore
            }
        }
    }
}

E, infine, la vista.

Nota: per semplicità, ho utilizzato il componente SwiftUI RemoteImage per visualizzare l'immagine, proprio come ho usato Glide su Android.

 struct ContentView: Visualizza {
 
    @ObservedObject private(set) var viewModel: ViewModel
    
    var body: alcuni Visualizza {
        comicView()
    }
    --
    private func comicView() -> alcuni Visualizza {
        cambia viewModel.comic {
        caricamento del caso:
            return AnyView(Text("Caricamento in corso"))
        case .result (lascia comico):
            return AnyView(VStack {
                Testo(titolo.fumetto)
                RemoteImage(url: comic.img)
            })
        caso .errore:
            return AnyView(Text("Errore"))
        }
    }
}

E il gioco è fatto, anche l'app per iOS è pronta!

Un'applicazione iOS sviluppata con KMM

Riepilogo

Infine, per rispondere alla domanda del titolo: Kotlin Multiplatform è il futuro dello sviluppo multipiattaforma? – tutto dipende dalle esigenze. Se vuoi creare una piccola app identica per entrambe le piattaforme mobili contemporaneamente, probabilmente no, perché devi avere le conoscenze necessarie sullo sviluppo per entrambe le piattaforme.

Rilascio dell'icona del prodotto

Sviluppa la tua prossima app con i nostri esperti

Ottieni un preventivo

Tuttavia, se hai già un team di sviluppatori Android e iOS e desideri offrire la migliore esperienza utente, puoi ridurre notevolmente i tempi di sviluppo . Come nell'esempio fornito, grazie a un modulo condiviso, la logica dell'applicazione è stata implementata una sola volta e l'interfaccia utente è stata creata in modo completamente specifico per la piattaforma. Allora perché non provarlo? Come puoi vedere, è facile iniziare.

Curioso dello sviluppo multipiattaforma dal punto di vista del business? Dai un'occhiata al nostro articolo sui vantaggi dello sviluppo multipiattaforma.