Czy Kotlin Multiplatform to przyszłość rozwoju wieloplatformowego? Wskazówki, jak zacząć
Opublikowany: 2021-01-29W dzisiejszych czasach możemy zaobserwować trend w rozwoju aplikacji mobilnych, aby szybciej publikować aplikacje. Podejmowano wiele prób skrócenia czasu rozwoju poprzez współdzielenie wspólnych części kodu między różnymi platformami, takimi jak Android i iOS. Niektóre rozwiązania zyskały już popularność, inne są wciąż w fazie rozwoju. Dzisiaj chciałbym omówić jedno z najnowszych podejść z drugiej grupy – Kotlin Multiplatform Mobile (w skrócie KMM).
Czym jest Multiplatformowa Mobilna Kotlin?
KMM to pakiet SDK, którego głównym celem jest udostępnianie logiki biznesowej między platformami – część, która w większości przypadków i tak musi być taka sama. Osiąga się to dzięki zestawowi wielu kompilatorów dla współdzielonego modułu. Na przykład, cel Androida używa wariantu Kotlin / JVM, a dla iOS istnieje Kotlin / Native. Współdzielony moduł można następnie dodać do typowych projektów aplikacji natywnych, a programiści odpowiedzialni za interfejs użytkownika mogą skupić się na zapewnieniu użytkownikom najlepszych wrażeń w znanym im środowisku – Android Studio na Androida i Xcode na iOS.
Multiplatforma Kotlina kontra Flutter
Obecnie jednym z najpopularniejszych rozwiązań do tworzenia aplikacji wieloplatformowych jest Flutter. Koncentruje się na zasadzie „napisz jedną aplikację i uruchamiaj ją wszędzie” – która działa, ale tylko dla prostych aplikacji. W rzeczywistych scenariuszach programiści i tak często muszą napisać kod natywny dla każdej platformy, aby wypełnić luki , na przykład, gdy brakuje jakiejś wtyczki. Dzięki takiemu podejściu aplikacja wygląda tak samo na różnych platformach, co czasami jest pożądane, ale w niektórych przypadkach może łamać określone wytyczne projektowe.
Gotowy do zbudowania własnej aplikacji?
Wybierz TrzepotanieChoć mogą brzmieć podobnie, Kotlin Multiplatform nie jest rozwiązaniem wieloplatformowym – nie próbuje wymyślać koła na nowo. Deweloperzy nadal mogą korzystać z narzędzi, które znają i lubią. Upraszcza tylko proces ponownego wykorzystywania części kodu , które wcześniej powinny być napisane wielokrotnie, np. tworzenie żądań sieciowych, przechowywanie danych i inna logika biznesowa.
Plusy i minusy Multiplatformy Kotlin
Plusy KMM :
- Opracowana aplikacja jest w 100% natywna dla każdej platformy – łatwo ją zintegrować z aktualnie używanym kodem i bibliotekami firm trzecich
- Łatwy w użyciu – prawie wszyscy programiści Androida używają już Kotlina, więc do rozpoczęcia pracy potrzeba bardzo niewiele dodatkowej wiedzy
- UI można podzielić na każdą platformę docelową – aplikacja będzie spójna z dowolnym ekosystemem
- Wspólna logika pozwala programistom dodawać nowe funkcje i naprawiać błędy w obu systemach operacyjnych jednocześnie
Wady KMM :
- Wiele komponentów jest nadal w fazie alfa/beta i potencjalnie może być niestabilna lub ulec zmianie w przyszłości
Jakie firmy korzystają z KMM?
Według oficjalnej strony firmy coraz częściej interesują się tą technologią, a lista stale się wydłuża. Wśród nich są tak znane marki jak Autodesk, VMWare, Netflix czy Yandex.
Jak zacząć korzystać z Multiplatformy Kotlin?
Najlepszym miejscem do nurkowania w celu uzyskania szczegółowych informacji jest oficjalny przewodnik, ale w tym artykule chciałbym pokazać przykład, który jest dość prosty, ale ciekawszy niż tylko „Hello World”, którym byłoby pobieranie i wyświetlanie aplikacji najnowszy komiks autorstwa Randalla Munroe (na licencji CC BY-NC 2.5) z tytułem z xkcd.com API.
Funkcje do omówienia:
- Konfiguracja projektu
- Sieć w module współdzielonym
- Prosty interfejs użytkownika dla systemu Android i iOS
Uwaga: Chciałem, aby ten przykład był równie łatwy do odczytania zarówno dla programistów Androida, jak i iOS, więc w niektórych miejscach celowo pominąłem niektóre dobre praktyki specyficzne dla platformy, aby było jasne, co się dzieje
Konfiguracja projektu
Najpierw upewnij się, że masz zainstalowane najnowsze wersje Android Studio i Xcode , ponieważ obie będą niezbędne do zbudowania tego projektu. Następnie w Android Studio zainstaluj wtyczkę KMM. Ta wtyczka upraszcza wiele rzeczy – aby utworzyć nowy projekt, po prostu kliknij Utwórz nowy projekt i wybierz aplikację KMM.
Po utworzeniu projektu przejdź do pliku build.gradle.kts
w katalogu udostępnionym . Tutaj musisz określić wszystkie wymagane zależności. W tym przykładzie użyjemy ktor dla warstwy sieciowej, kotlinx.serialization
do analizowania odpowiedzi json z zaplecza i współprogramów kotlin, aby zrobić to wszystko asynchronicznie.
Dla uproszczenia poniżej zamieszczam zestawienia pokazujące wszystkie zależności, które należy dodać do już obecnych. Kiedy dodajesz zależności, po prostu zsynchronizuj projekt (pojawi się monit). Najpierw dodaj wtyczkę serializacji do sekcji wtyczek.
wtyczki { kotlin("plugin.serialization") wersja "1.4.0" }
Następnie dodaj zależności.
Zbiory źródeł { val commonMain poprzez uzyskanie { zależności { implementacja("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0") implementacja("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt-2") implementacja("io.ktor:ktor-client-core:1.4.1") implementacja("io.ktor:ktor-client-json:1.4.1") implementacja("io.ktor:ktor-klient-serializacja:1.4.1") } } val androidMain poprzez uzyskanie { zależności { implementacja("io.ktor:ktor-klient-android:1.4.1") } } val iosMain poprzez uzyskanie { zależności { implementacja("io.ktor:ktor-client-ios:1.4.1") } } }
Warto wspomnieć, że w momencie pisania tego artykułu pojawiły się pewne problemy ze stabilną wersją biblioteki coroutines na iOS – dlatego używana wersja ma sufiks native-mt-2 (co oznacza natywną wielowątkowość). Tutaj możesz sprawdzić aktualny stan tego problemu.
Sieć w module współdzielonym
Po pierwsze potrzebujemy klasy reprezentującej odpowiedź – te pola są obecne w jsonie zwracanym przez backend.
importować kotlinx.serialization.Serializable @serializowalny klasa danych XkcdResponse( val img: ciąg, tytuł val: String, dzień roboczy: wewn., wart miesiąc: Int, val rok: Int, )
Następnie musimy stworzyć klasę reprezentującą API z klientem HTTP . W przypadku, gdy nie podaliśmy wszystkich pól obecnych w json, możemy użyć właściwości ignoreUnknownKeys
, aby serializator mógł zignorować brakujące pola. Ten przykład ma tylko jeden punkt końcowy reprezentowany przez zawieszoną funkcję. Ten modyfikator informuje kompilator, że ta funkcja jest asynchroniczna. Opiszę to bardziej za pomocą kodu specyficznego dla platformy.
importuj io.ktor.client.* importuj io.ktor.client.features.json.* importuj io.ktor.client.features.json.serializer.* importuj io.ktor.client.request.* klasa XkcdApi { private val baseUrl = "https://xkcd.com" wartość prywatna httpClient = HttpClient() { zainstaluj(JsonFeature) { serializator = KotlinxSerializer( kotlinx.serializacja.json.Json { zignorujNieznaneKlawisze = prawda } ) } } zawiesić zabawę fetchLatestComic() = httpClient.get<XkcdResponse>("$baseUrl/info.0.json") }
Gdy nasza warstwa sieciowa jest gotowa, możemy przejść do warstwy domeny i stworzyć klasę reprezentującą lokalny model danych. W tym przykładzie pominąłem więcej pól i pozostawiłem tylko tytuł komiksu i adres URL obrazu.
klasa danych ComicModel( val imageUrl: Ciąg, tytuł wartości: Ciąg )
Ostatnią częścią tej warstwy jest utworzenie przypadku użycia, który wyzwoli żądanie sieciowe, a następnie zmapuje odpowiedź na model lokalny.
class GetLatestComicUseCase(wartość prywatna xkcdApi: XkcdApi) { zawiesić zabawę run() = xkcdApi.fetchLatestComic() .let { ComicModel(it.img, it.title) } }
Prosty interfejs użytkownika dla Androida
Czas przejść do katalogu androidApp
– jest to miejsce, w którym przechowywana jest natywna aplikacja na Androida. Najpierw musimy dodać pewne zależności specyficzne dla Androida do drugiego pliku build.gradle.kts
znajdującego się tutaj. Ponownie, poniższa lista pokazuje tylko zależności, które należy dodać do już istniejących. Ta aplikacja będzie używać architektury Model-View-ViewModel (pierwsze dwie linie) i Glide, aby załadować komiks ze zwróconego adresu URL (dwie drugie linie)

zależności { implementacja("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0") implementacja("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0") implementacja("com.github.bumptech.glide:glide:4.11.0") adnotationProcessor("com.github.bumptech.glide:compiler:4.11.0") }
Domyślnie nowo utworzony projekt powinien zawierać MainActivity i jego plik układu activity_main.xml
. Dodajmy do niego kilka widoków — jeden TextView
dla tytułu i jeden ImageView dla samego komiksu.
<?xml version="1.0" kodowanie="utf-8"?> <LinearLayout xmlns:andro android: android:layout_width="match_parent" android:layout_height="match_parent" android:grawitacja="środek" android:orientacja="pionowa"> <WidokTekstowy android: android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Widok obrazu android: android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
Wtedy będziemy potrzebować jakiejś reprezentacji stanu aplikacji – może to być ładowanie nowego komiksu, wyświetlanie go lub możemy napotkać błąd podczas ładowania.
klasa zapieczętowana Stan { Wczytywanie obiektu : Stan() class Success(val wynik: ComicModel) : Stan() Błąd obiektu : Stan() }
Teraz dodajmy minimalny ViewModel, korzystając z wcześniej utworzonej logiki biznesowej . Wszystkie klasy mogą być importowane. MutableLiveData
jest polem obserwowalnym – widok będzie obserwował zmiany i odpowiednio się aktualizował. viewModelScope
to współprogram powiązany z cyklem życia viewmodelu – w przypadku zamknięcia aplikacji automatycznie anuluje oczekujące zadania.
class MainViewModel : ViewModel() { private val getLatestComicUseCase = GetLatestComicUseCase(XkcdApi()) val komiks = MutableLiveData<State<ComicModel>>() zabawa pobierzKomiks() { viewModelScope.launch { comic.value = Stan.Loading() runCatching { getLatestComicUseCase.run() } .onSuccess { comic.value = State.Success(it) } .onFailure { comic.value = State.Error() } } } }
Ostatnia rzecz – MainActivity, aby wszystko połączyć.
klasa MainActivity : AppCompatActivity(R.layout.activity_main) { private val viewModel: MainViewModel autorstwa lazy { ViewModelProvider(this).get(MainViewModel::class.java) } zastąp zabawę onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) zobaczModel.komiks.obserwuj(to) { kiedy to) { to State.Loading -> { findViewById<TextView>(R.id.titleLabel).text = "Ładowanie" } to Stan.Sukces -> { findViewById<TextView>(R.id.titleLabel).text = it.result.title Glide.z(tym) .load(it.result.img) .into(findViewById(R.id.image)) } to stan.Błąd -> { findViewById<TextView>(R.id.titleLabel).text = "Błąd" } } } viewModel.fetchComic() } }
To wszystko, aplikacja na Androida jest gotowa!
Prosty interfejs użytkownika dla iOS
Wszystko powyżej zostało zrobione w Android Studio, więc w tej części przejdźmy do Xcode, aby było wygodniej. Aby to zrobić, po prostu otwórz Xcode i wybierz katalog iosApp – zawiera on wstępnie skonfigurowany projekt Xcode. Domyślnie ten projekt używa SwiftUI dla GUI, więc trzymajmy się go dla uproszczenia.
Pierwszą rzeczą do zrobienia jest stworzenie podstawowej logiki do pobierania danych komiksowych. Tak jak poprzednio, potrzebujemy czegoś do reprezentowania państwa.
wyliczenie stan { ładowanie sprawy sprawa powodzenie (ComicModel) przypadek błędu }
Następnie ponownie przygotujmy ViewModel
class ViewModel: ObservableObject { niech getLatesteComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi()) @Opublikowano var comic = State.loading w tym() { własny.komiks = .ładowanie getLatestComicUseCase.run { fetchedComic, błąd w if fetchedKomiks != zero { self.comic = .success(pobranyKomiks!) } w przeciwnym razie { self.comic = .błąd } } } }
I wreszcie widok.
Uwaga: dla uproszczenia użyłem komponentu SwiftUI RemoteImage do wyświetlenia obrazu, tak jak użyłem Glide na Androidzie.
struct ContentView: Widok { @ObservedObject private(set) var viewModel: ViewModel var body: niektóre Widok { komiksView() } -- private func comicView() -> niektóre widoki { przełącz viewModel.comic { sprawa .ładowanie: return AnyView(Text("Ładowanie")) case .result(niech komiks): return AnyView(VStack { Tekst(komiks.tytuł) Obraz zdalny(url: komiks.img) }) przypadek .błąd: return AnyView(Text("Błąd")) } } }
I to wszystko, aplikacja na iOS też jest gotowa!
Streszczenie
Na koniec odpowiem na tytułowe pytanie – Czy Kotlin Multiplatform to przyszłość rozwoju wieloplatformowego? – wszystko zależy od potrzeb. Jeśli chcesz stworzyć małą, identyczną aplikację na obie platformy mobilne jednocześnie, to prawdopodobnie nie, ponieważ musisz mieć niezbędną wiedzę na temat programowania na obie platformy.
Opracuj kolejną aplikację z naszymi ekspertami
Uzyskaj wycenęJeśli jednak masz już zespół programistów Androida i iOS i chcesz zapewnić użytkownikom najlepsze wrażenia, może to znacznie skrócić czas programowania . Podobnie jak w podanym przykładzie, dzięki współdzielonemu modułowi, logikę aplikacji zaimplementowano tylko raz, a interfejs użytkownika został stworzony w sposób w pełni specyficzny dla platformy. Dlaczego więc nie spróbować? Jak widać, rozpoczęcie pracy jest łatwe.
Ciekawi Cię rozwój wieloplatformowy z perspektywy biznesowej? Zapoznaj się z naszym artykułem na temat zalet rozwoju międzyplatformowego.