Jedna aplikacja od razu z GMS i HMS – jak to zrobić?

Programowanie

Witajcie ponownie. W dzisiejszym artykule postanowiłem przybliżyć nieco zagadnienie połączenia w jednej aplikacji dwóch różnych usług – GMS i HMS. Zastanowimy się nad najlepszymi rozwiązaniami oraz jakie są ich skutki. Możliwości jest co najmniej kilka i jestem pewien, że można to zrobić na kilka innych sposobów, których pewnie tutaj nie poruszę. Jeśli znajdziecie fajne rozwiązanie, które można zastosować dla połączenia lub rozdzielenia konkurencyjnych bibliotek, to serdecznie zapraszam do komentowania.

W jednej aplikacji możesz zastosować GMS i HMS

aplikacje na hms jak napisac poradnik

Tak jak wspominałem w poprzednich artykułach, stanąłem przed zadaniem dostosowania aplikacji Call2Car, aby działała zarówno na urządzeniach mających dostęp do GMS, jak i do tych, które stawiają wyłącznie na ekosystem Huawei, czyli HMS. Jeśli stanęliście już kiedyś przed podobnym problemem, to pewnie automatycznie przychodzą Wam już do głowy pewne schematy takiego postępowania. Zastanówmy się nad kilkoma z nich i zobaczmy, jakie będą ich konsekwencje.

Nie przegap
Huawei Mobile Services – wszystko, co musisz wiedzieć o HMS w jednym miejscu
Huawei Mobile Services hms uslugi co to jest
Rosnąca popularność Huawei Mobile Services, w skrócie HMS, a w Polsce Mobilne Usługi Huawei to nic innego jak wypadkowa rozwoju jednego z największych graczy na mobilnym rynku. Perturbacje światowych mocarstw często zbierają żniwa tam, gdzie byśmy się tego nie spodziewali. Stany Zjednoczone i Chiny wciąż szukają porozumienia, choć najbardziej poszkodowanym wydaje się właśnie Huawei. Gigant […]

Rozwiązania najprostsze są często najlepsze, czyli zacznijmy od if

W pierwszym rozwiązaniu nasza aplikacja wynikowa będzie zawierała zarówno kod dla GMS, jak i HMS. Wybór użycia będzie zależał od tego, który z nich jest dostępny na urządzeniu. Prosty if potrafi przecież zdziałać cuda.

Dodajmy najpierw nasze zależności do plików gradle:

build.gradle:

buildscript {
    
    ext {
        googleMapsVersion = '17.0.0'
        gcmVersion = '17.0.0'
        huaweiMapsVersion = '4.0.0.302'
        huaweiPushVersion = '4.0.2.300'
    }
}

app/build.gradle

dependencies {
    implementation "com.google.android.gms:play-services-maps:$rootProject.googleMapsVersion"
    implementation "com.google.android.gms:play-services-gcm:$rootProject.gcmVersion"
    implementation "com.huawei.hms:maps:$rootProject.huaweiMapsVersion"
    implementation "com.huawei.hms:push:$rootProject.huaweiPushVersion"
}

apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect'

Mając dodane nasze zależności, zastanówmy się, co tak naprawdę musimy rozdzielić. W moim projekcie używam obecnie map zadeklarowanych w plikach layout. Pliki XML mają to do siebie, że niestety nie pozwalają nam wybrać typu widoku w zależności od możliwości urządzenia. Mamy więc tutaj trzy sposoby:

  • zadeklarować oba widoki, aby ukryć jeden z nich,
  • utworzyć jedynie kontener, a potem wrzucić tam widok map używając kodu Javy lub Kotlina,
  • utworzyć 2 osobne pliki, z których w jednym użyjemy mapy Google, a w drugiej Huawei.

Ja postanowiłem wybrać ostatnią opcję. Omijamy tutaj komplikowanie logiki samej aplikacji, przez co jest mniejsze prawdopodobieństwo popełnienia błędu. Sama architektura jest czystsza i przez to jej późniejsze utrzymanie jest mniej kosztowne.

Moje pliki nazwę roboczo map_google.xml oraz map_huawei.xml.

map_layout_google.xml:

<?xml version="1.0" encoding="utf-8"?>
    <com.google.android.gms.maps.MapView
        android:id="@+id/mapView"
	.../>

map_layout_huawei.xml

<?xml version="1.0" encoding="utf-8"?>
    <com.huawei.hms.maps.MapView
        android:id="@+id/mapView"
       .../>

Przejdźmy zatem do kodu – mój będzie w języku Kotlin.

val gmsStatus = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
val hmsStatus = HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context)
if(hmsStatus == ConnectionResult.SUCCESS) {
    val view = inflater.inflate(R.layout.maps_huawei, container, false)
} else if(gmsStatus == ConnectionResult.SUCCESS) {
    val view = inflater.inflate(R.layout.maps_google, container, false)
}

Czy łączenie GMS i HMS w jednej aplikacji to więcej szkody niż pożytku

huawei mobile services hms co to 1

To, co od razu rzuca się w oczy w powyższym przykładzie to to, że za każdym razem nasz kod musimy poprzedzać takimi warunkami. Jeśli aplikacja jest prosta, to kodu nie będzie dużo, ale i tak zmniejsza się jego czytelność. Jest to w takim razie coś, czego powinniśmy unikać. Rozwiązanie takie, choć może kusić prostotą, powoduje także inne problemy, między innymi:

  1. Konieczność kompilacji wszystkich bibliotek podczas budowania. Nasza aplikacja rośnie, choć jej użytkownicy prawdopodobnie nie będą w stanie użyć jej w 100%.
  2. Nieważne czy budujemy pod GMS, czy pod HMS używamy pluginów google-services oraz agconnect. Rośnie przez to czas budowania.
  3. Jeśli używamy Push, w pliku AndroidManifest.xml musimy zadeklarować service. Tutaj nie ma możliwości wrzucenia if i musimy włączyć obie usługi, mimo że tak naprawdę jedynie jedna z nich może działać poprawnie.

Jedna aplikacja? Może stworzyć dwie, osobno dla GMS i HMS?

jak napisac aplikacje hms core poradnik rejestracja

Potrzebujemy podejścia wolnego od powyższych problemów. Rozwiązaniem może być użycie productFlavors. Flavors zadebiutowały już dość dawno temu, a ich możliwości są stale rozwijane. Pozwalają one tworzyć osobne wersje aplikacji, korzystające ze wspólnego kodu bazowego. W moim przypadku stworzyłem dwa różne ‘smaki’: gms oraz hms. Tak wygląda w tej chwili moja deklaracja w pliku gradle:

android {
    buildTypes {
        flavorDimensions "version"
        productFlavors {
            gms {
                dimension "version"
            }
            hms {
                dimension "version"
            }
        }
    }
}

Zastanawiałem się przez jakiś czas, jak najlepiej opisać dalsze kroki? Najlepszym rozwiązaniem wydało mi się zacząć od pokazania struktury katalogów, którą stworzyłem u siebie. Prezentuje się ona następująco:

jedna aplikacja gms hms jak to zrobic programowanie

Na powyższym urywku zrzutu ekranu widać, że moja struktura uległa rozwidleniu. W katalogu src, oprócz standardowego main, mam także gms oraz hms. Nazwy te odpowiadają oczywiście tym zadeklarowanym powyżej w sekcji productFlavors pliku build.gradle. W moich katalogach widać już dwa typy zasobów:

  • Pierwszy to pliki map_layout.xml, które zawierają deklaracje map. Podobnie jak na początku artykułu, w każdym z nich używam deklaracji widoków zgodnych z docelową platformą. W odróżnieniu jednak od pierwszego rozwiązania tym razem pliki mają tę samą nazwę.
  • Drugim zasobem są klasy MessagingService, które są odpowiedzialne za powiadomienia. Tak samo jak w przypadku plików xml obie klasy mają tę samą nazwę.

Android Studio posiada wbudowane wsparcie dla productFlavors. Możemy z łatwością wybrać, którą z wersji aktualnie chcemy rozwijać.

jedna aplikacja gms hms jak to zrobic programowanie

Czasem kuszące jest skopiowanie dużej klasy lub layoutu w całości i zmiana jedynie importów. Może się jednak okazać, że szykujemy pułapkę na siebie. Jeśli w przyszłości dojdzie do konieczności zmiany,  będziemy musieli wykonać ją w dwóch miejscach. W ekstremalnych przypadkach okaże się, że piszemy już nie jedną, ale dwie aplikacje równolegle. Jeśli nie jest to rzeczywiście konieczne i uzasadnione, trzymajmy jak najwięcej kodu w main.

Nic się nie zmieniło? Nadal mamy te same problemy z łączeniem GMS i HMS w jednej aplikacji

Rozdzieliliśmy już nasz kod, ale co nam to dało? Czy któryś ze wspomnianych wcześniej problemów został rozwiązany? Odpowiedź brzmi: tak. Dzięki użyciu tej samej nazwy dla serwisu Push tylko jedna z tych klas zostanie skompilowana i tylko jedna z nich zostanie zadeklarowana jako <service> w pliku AndroidManifest.xml.

Co z pozostałymi problemami? Na początku zajmijmy się kompilacją samych bibliotek. Gradle ma całkiem przyjemne rozwiązanie naszego problemu. Możemy zmodyfikować nasz import tak, aby był wywoływany jedynie dla odpowiedniego ‘smaku’.

dependencies {
    gmsImplementation "com.google.android.gms:play-services-maps:$rootProject.googleMapsVersion"
    gmsImplementation "com.google.android.gms:play-services-gcm:$rootProject.gcmVersion"

    hmsImplementation "com.huawei.hms:maps:$rootProject.huaweiMapsVersion"
    hmsImplementation "com.huawei.hms:push:$rootProject.huaweiPushVersion"
}

Rozwiązanie to jest nie tylko eleganckie, ale także skuteczne. Mnie osobiście zachwyciło w swojej prostocie. Żałuję tylko, że podobnego rozwiązania nie zastosowano w przypadku uruchamiania pluginów. Przyznam szczerze, że szukałem przez pewien czas rozwiązania tego problemu. Takich rozwiązań jest kilka – jedne są brzydkie a inne… jeszcze brzydsze. W moim przypadku postawiłem na if. Jeśli znacie coś lepszego, proszę, podzielcie się w komentarzach.

Plik app/build.gradle

def pluginVersion = ""

android {
    buildTypes {
        flavorDimensions "version"
        productFlavors {
            gms {
                pluginVersion = "gms"
                dimension "version"
            }
            hms {
                pluginVersion = "hms"
                dimension "version"
            }
        }
    }
}

if (pluginVersion == "gms")
    gmsApply plugin: 'com.google.gms.google-services'
if (pluginVersion == "hms")
    hmsApply plugin: 'com.huawei.agconnect'

Jak zbudować aplikację z GMS i HMS?

jedna aplikacja gms hms jak to zrobic programowanie

Mój kod został rozdzielony. Oznacza to, że w wyniku budowania dostanę inny plik do sklepu Google, a inny do Huawei. Można w tym celu wykorzystać wbudowany w Android Studio generator, który jest dostępny z poziomu menu: Build →  Generate Signed Bundle /APK… W kilku krokach można wyklikać sobie potrzebną wersję. Jeśli mamy skonfigurowany signingConfig, wielbiciele terminala szybciej zrobią to przy pomocy poleceń:

  • gradlew assembleHmsRelease
  • gradlew bundleGmsRelease

Zobacz też: Recenzja Huawei P40 Pro – w wielu aspektach po prostu najlepszy.

Przedstawiłem tutaj sposób na połączenie ze sobą HMS i GMS. Nie jest to praca skomplikowana, a zwiększa potencjalne grono odbiorców aplikacji o kilkadziesiąt milionów. W przyszłości skupię się na eksploracji usług związanych z HMS. Wydaje mi się to dobrym pomysłem, biorąc pod uwagę to, że Huawei dopiero się rozwija i zachęca programistów na różne sposoby. Usługi są w tej chwili bezpłatne, co może mieć niebagatelny wpływ na rozwój Waszych aplikacji. Sam sklep AppGallery stosuje także wiele mechanizmów promocji, które pozwalają na szybsze dotarcie do szerokiego grona użytkowników. Pamiętajmy, że kto stoi w miejscu, ten się cofa.

Tymczasem miejcie na względzie wydarzenie, które niedługo będzie miało miejsce, czyli Huawei Developer Day, które przybliży Wam świat HMS i wszystko, co z nim związane.






Przewiń stronę, by przeczytać kolejny wpis
x