Czy Kotlin Multiplatform to przyszłość rozwoju wieloplatformowego? Wskazówki, jak zacząć

Opublikowany: 2021-01-29

W 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.

Ikona wieloplatformowych usług programistycznych

Gotowy do zbudowania własnej aplikacji?

Wybierz Trzepotanie

Choć 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.

Utwórz nowy projekt Kotlin Multiplatform Mobile

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!

Aplikacja na Androida opracowana przy użyciu KMM

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!

Aplikacja na iOS opracowana przy użyciu KMM

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.

Zwalniam ikonę produktu

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.