70

How does one go about having both Google Mobile Services and Huawei Mobile Services in the app?

Being that Huawei have lost the license over GMS, it seems we need to replace all the GMS services used in the apps with Huawei provided ones. What would a "best practice" be for this? Use flavors and somehow handle each class individually, or copy paste the project and start replacing? Or ... better yet, is there a way to perhaps have both and ... somehow let the app decide which service to use based on the device it's on? Obviously the last one would presume an increase in the APK file size.

Any ideas?

Chetan Joshi
  • 5,582
  • 4
  • 30
  • 43
AndreiBogdan
  • 10,858
  • 13
  • 58
  • 106
  • any update on this? Finally how did you managed? What about Firebase analytics and/or Admob? Isn't againts Google/Admob terms and conditions? I'm asking because I don't want to get banned from Google Play Dev and from Admob. – Zbarcea Christian May 16 '20 at 14:41
  • Hi. Check my approved answe. I answered my own question. Hope it helps someone ... Cheers. – AndreiBogdan May 16 '20 at 15:49

6 Answers6

59

So, I managed to do it like this:

Defined two flavours

    gms {
        dimension "services"
        buildConfigField "String", "SERVICE_USED", '"g"'

    }
    hms {
        dimension "services"
        buildConfigField "String", "SERVICE_USED", '"h"'
    }

I use the "g" and "h" in the code whenever I need to decide on doing things like: the API requires a deviceType of "android" or "iOS" and with the inclusion of the Huawei build we defined another constant "huawei". I use SERVICE_USED to know what constant to send.

I then did this at the top of the build.gradle:

apply plugin: 'com.android.application'
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")) {
    //*meh*
} else {
    apply plugin: 'io.fabric'
}

because I was using fabric (and fabric / firebase ... don't really work with HMS) and I also did this at the very bottom of the build.gradle

if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")) {
    apply plugin: 'com.huawei.agconnect'
} else {
    apply plugin: 'com.google.gms.google-services'
}

to only include the proper plugin.

I then started handling each thing that was using gms (maps, location, push notifications, analytics ) by making a wrapper and separating the code in each flavour. i.e. for push notifications i created a HPushNotif which has an getToken method. I define the same class and method in both flavours but I implement them according to the type of service (gms or hms).

I used this type of notation when including dependencies in the project:

//GMS stuff
gmsImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
gmsImplementation 'com.google.firebase:firebase-core:16.0.9'
gmsImplementation 'com.google.firebase:firebase-messaging:18.0.0'
gmsImplementation 'com.google.firebase:firebase-crash:16.2.1'
gmsImplementation 'com.google.android.gms:play-services-maps:16.1.0'
gmsImplementation 'com.google.android.gms:play-services-location:16.0.0'
gmsImplementation 'com.google.android.gms:play-services-tagmanager:16.0.8'

//HMS stuff
hmsImplementation 'com.huawei.agconnect:agconnect-core:1.0.0.300'
hmsImplementation 'com.huawei.hms:push:4.0.3.301'
hmsImplementation 'com.huawei.hms:maps:4.0.1.301'
hmsImplementation 'com.huawei.hms:location:4.0.3.303'

The gms and hms before the Implementation refer to the name of the flavours. Those dependencies will only be loaded when the appropriate BuildVariant is selected (i.e. appropriate flavour is being built).

Basically I wrapped the logic for maps, analytics, location and push notifications for both cases. This is how the structure looks. Nothing special.

That's it. When they created HMS they basically copied GMS class by class and methd by method. You'll see that the exact method names match exactly, to the calling parameters even and returning values. They're 99.99% the same. That makes things easier. Basically you just need to copy the code in two classes and import the proper things (at the top of the class). You rarely need to change the code you've already written for GMS.

Hope it helps someone.

enter image description here

AndreiBogdan
  • 10,858
  • 13
  • 58
  • 106
  • 1
    In my case I did not find alternative to `com.google.android.gms.common.api.ApiException` and `com.google.android.gms.common.api.ResolvableApiException` which are needed to implement dialog known from google maps to turn GPS on in-app (without redirection to settings) https://www.google.com/search?q=google+play+services+request+turn+on+gps&source=lnms&tbm=isch When I checked huawei "Petal Maps" there is just alert that GPS is not enabled and no dialog to turn on it in-app. So I do not thing that 99.99% similarity is true ;) – mikep Apr 23 '21 at 12:07
  • 1
    @mikep in hms you can use com.huawei.hms.common.ApiException and com.huawei.hms.common.ResolvableApiException – emre Sep 14 '21 at 11:03
  • getGradle().getStartParameter().getTaskRequests().toString().contains("Hms") it returns false for me – Vahit Keskin Jun 15 '22 at 14:34
43

Before I answer your question here is short explanation what is HMS and GMS:

  • HMS stands for Huawei Mobile Services
  • GMS stands for Google Mobile Services

You can publish your app (which is using Google's libraries) in Huawei's app store (named AppGallery) but this app will be visible and available to download only for Huawei's devices containing HMS+GMS (all devices till 2020 had HMS and GMS).

However the newer phones i.e. Mate 30 series, P40 - will have installed only HMS. So if you want to make your app visible for all Huawei devices (HMS+GMS and HMS) then you will have to implement in you app function for detecting what service is on on user's device. It will decide what proper function to call (i.e initialize instance of Huawei Maps or Google Maps).

Here is the code for detecting HMS and GMS:

For Huawei Mobile Services we use:

HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context);

https://developer.huawei.com/consumer/en/doc/development/HMS-References/huaweiapiavailability

For Google Mobile Services we use:

GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);

https://developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability

Here is the code how to properly handle detecting HMS and GMS:

public static boolean isHmsAvailable(Context context) {
    boolean isAvailable = false;
    if (null != context) {
        int result = HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context);
        isAvailable = (com.huawei.hms.api.ConnectionResult.SUCCESS == result);
    }
    Log.i(TAG, "isHmsAvailable: " + isAvailable);
    return isAvailable;
}

public static boolean isGmsAvailable(Context context) {
    boolean isAvailable = false;
    if (null != context) {
        int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
        isAvailable = (com.google.android.gms.common.ConnectionResult.SUCCESS == result);
    }
    Log.i(TAG, "isGmsAvailable: " + isAvailable);
    return isAvailable;
}

AFAIK these classes (HuaweiApiAvailability/GoogleApiAvailability) are available if you implement any of the Huawei's kit/Google's lib.

deadfish
  • 11,996
  • 12
  • 87
  • 136
  • 1
    My app support different package name for release and debug build to be able to install both apps on the same device. When I try to integrate com.huawei.agconnect I get error that package_name from agconnect-services.json (for example: my.package.app) doesn't equals to real my.package.app.dev for debug build. And agconnect-services.json doen't support many packages as google-services.json – cosic Apr 01 '20 at 12:32
  • 8
    To have access to `HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context)` you need only `implementation 'com.huawei.hms:base:4.0.2.300'` and add `maven {url 'http://developer.huawei.com/repo/'} ` to repositories – cosic Apr 01 '20 at 14:38
  • For a more advanced detection including giving the user the possibility to react when HMS/GMS need install/update take a look here: https://github.com/abusuioc/from-gms-to-hms#detect-at-runtime-what-mobile-services-are-available-on-the-device – Adi B Apr 08 '21 at 13:31
  • Cosic, I believe u have to create two different projects inside the huawei console to have two different agconnect-services.json files. As I read here, it looks possible: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-config-flavor-android-0000001057944603 – Ramiro G.M. Apr 04 '22 at 19:37
  • Great answer! Just a note that you could also write these methods in one line: ``return HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context) == ConnectionResult.SUCCESS`` – Jorn Rigter May 05 '22 at 10:37
11

While it really depends on architecture of your app, there are 2 reasonable alternatives so far;

  1. Using flavors and variants, this will give you more flexibility. Establishing the architecture and implementation would be relatively more time consuming yet it is a clean approach providing a nice isolation of code. Since those ecosystems has different markets (AppGallery for Huawei), with flavors and variants, it is quite handy to establish separate build pipelines. It gives you the ability of maintaining different apk for different ecosystem
  2. Using a wrapper/bridge approach. Simply, implement the wrapper classes to decide and forward requests to corresponding endpoints. With this approach, it is possible to maintain single for both markets. HMS actually provides a robust tool for this. It analyzes the code which depends on GMS, then automatically generates wrapper classes and converts the original code to use wrapper classes. It is called "HMS Converter" and has an Android Studio plugin even. https://developer.huawei.com/consumer/en/huawei-toolkit/
captaink
  • 476
  • 3
  • 8
  • 4
    Do **not** use the second approach if your app will grow or is grown regarding the firbase/google library usage. We used the "HMS Converter" and the result was a build time of half an hour if you change a line in your gradle file. That is why we start to implement an abstraction layer controlled by variants and flavors. – user1185087 Apr 15 '20 at 12:12
  • do you have any particular finding on that long build time? is it really related to the variants and flavors it is generating, or is it a network issue on Huawei's repo? – captaink Apr 22 '20 at 10:30
  • If you wish to use open source wrapper libraries instead of the HMS converter, take a look at the current offering: https://github.com/abusuioc/from-gms-to-hms#wrappers – Adi B Apr 08 '21 at 13:33
8

Synthetizing all the good answers given before: https://github.com/abusuioc/from-gms-to-hms#step-5-integrate-hms-sdks-in-your-app

For most apps, a single build with dependencies on both GMS and HMS SDKs + deciding at runtime (based on the availability on the device) which one to use is the recommended way.

Adi B
  • 819
  • 1
  • 8
  • 11
  • 1
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](/help/deleted-answers) – STA Apr 08 '21 at 13:40
  • I guess your answer @EsTeAa is automatically generated. The original question is too generic to easily be answerable in a couple of sentences. The linked page provides a complete answer with code snippets. – Adi B Apr 08 '21 at 14:09
  • But you wish to be one? Otherwise I don't understand the tagline in your profile: "Was a human, now a Robot" :) I edited my answer anyway, I hope you'll find it now more useful, no need for silly downvotes. – Adi B Apr 08 '21 at 14:20
  • I work with SOBotics, anyway I didn't downvote for review – STA Apr 08 '21 at 15:08
8

One has to set up google and huawei as productFlavors and subsequently, as sourceSets.


Root project build.gradle:

buildscript {
    repositories {
        google()
        mavenCentral()
        maven { url "https://developer.huawei.com/repo/" }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.2.2"
        classpath "com.google.gms:google-services:4.3.13"
        classpath "com.huawei.agconnect:agcp:1.7.0.300"
    }
}

Module build.gradle:

plugins {
    id "com.android.application"
    id "androidx.navigation.safeargs"
}

def json_huawei_release = "src/huaweiRelease/agconnect-services.json"
def json_huawei_debug = "src/huaweiDebug/agconnect-services.json"
def json_google = "src/google/google-services.json"

if (getGradle().getStartParameter().getTaskRequests().toString().contains('Huawei')) {
    if (project.file(json_huawei_debug).exists() || project.file(json_huawei_release).exists()) {
        apply plugin: "com.huawei.agconnect"
    }
}

if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
    if (project.file(json_google).exists()) {
        println "found: ${project.file(json_google)}"
        apply plugin: "com.google.gms.google-services"
        apply plugin: "com.google.firebase.crashlytics"
    } else {
        println "missing: ${project.file(json_google)}"
    }
}

android {

    ...
    flavorDimensions "vendor"
    productFlavors {
        google {
            dimension "vendor"
            versionNameSuffix "-google"
        }
        huawei {
            dimension "vendor"
            versionNameSuffix "-huawei"
        }
    }

    sourceSets {
        google {
            java.srcDirs = ['src/main/java', 'src/google/java']
        }
        huawei {
            java.srcDirs = ['src/main/java', 'src/huawei/java']
        }
    }
}

dependencies {

    /** Google Play Services */
    googleImplementation "com.google.android.gms:play-services-base:18.0.1"
    googleImplementation "com.google.android.gms:play-services-basement:18.0.0"
    googleImplementation "com.google.android.gms:play-services-auth:20.0.0"
    googleImplementation "com.google.android.gms:play-services-identity:18.0.0"
    googleImplementation "com.google.android.gms:play-services-oss-licenses:17.0.0"

    /** Google Firebase */
    googleImplementation "com.google.firebase:firebase-auth:21.0.1"
    googleImplementation "com.google.firebase:firebase-database:20.0.3"
    googleImplementation "com.google.firebase:firebase-messaging:23.0.0"
    googleImplementation "com.google.firebase:firebase-functions:20.0.1"
    googleImplementation "com.google.firebase:firebase-crashlytics:18.2.6"
    googleImplementation "com.google.firebase:firebase-analytics:20.0.2"
    googleImplementation "com.google.firebase:firebase-perf:20.0.4"
    // googleImplementation "com.firebaseui:firebase-ui-auth:8.0.0"

    /** Huawei Mobile Services */
    huaweiImplementation "com.huawei.hms:base:6.1.0.302"
    huaweiImplementation "com.huawei.hms:push:6.1.0.300"
    huaweiImplementation "com.huawei.hms:hianalytics:6.3.0.300"

    /** Huawei AppGallery Connect */
    huaweiImplementation "com.huawei.agconnect:agconnect-core:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-auth:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-remoteconfig:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-function:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-cloud-database:1.5.2.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-applinking:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-crash:1.6.5.300"
    huaweiImplementation "com.huawei.agconnect:agconnect-apms:1.5.2.309"
    huaweiImplementation "com.huawei.agconnect:agconnect-storage:1.5.0.100"
    huaweiImplementation "com.huawei.agconnect:agconnect-appmessaging:1.6.5.300"
}

This permits to provide custom implementations for everything; it will build two different artifacts.

Switching build-variants and test-devices has to be considered when testing - but one can pass task-names and device serial-numbers in IDE run configurations (in order to run the correct build variant on the correct test device).

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
7

Both @AndreiBogdan and @deadfish's answer are correct. I'd like to add a little more:

First, you need to select a proper solution (G+H or G2H) based on the application scenario and development/test costs.

  1. If you choose G+H solution, you need to check whether the GMS is available. If the GMS interface cannot be used properly, HMS is required. For details, refer to @deadfish’s answer. You are advised to use this solution, which can
  • Reduce the complexity of app packaging. A package can be released to both Google Play and AppGallery.
  • Reduce the code maintenance cost. The HMS+GMS adaptation layer code is added to the original logic code. In this way, proper code can be automatically called based on the mobile phone. That is, you only need to invoke the method to check whether GMS is available on the existing logic code, and you don’t need to maintain two sets of code.
  1. If you choose G2H solution, the workload of compatibility test is small. You only need to test the new APK on Huawei phones. Release your app both on HUAWEI AppGallery and Google Play, with different packages. The app you release on AppGallery contains only Huawei's logic code. You may refer to @AndreiBogdan's answer, or see docs Supporting Multiple Channels.

  2. As @captaink say, you can use HMS Toolkit Convertor. It supports G+H and G2H conversion. Currently, HMS Toolkit supports Java and Kotlin. Supported Android Studio versions: 3.3.2~4.1.

zhangxaochen
  • 32,744
  • 15
  • 77
  • 108