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.
Before I answer your question here is short explanation what is HMS and GMS:
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).
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
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.
While it really depends on architecture of your app, there are 2 reasonable alternatives so far;
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.
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.
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.
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.
One can set up google
and huawei
as productFlavors
(essential) and use extra properties usesGms
and usesHms
, in order to apply the corresponding Gradle plugins (optional).
Root build.gradle
:
project.ext.set('usesGms', false)
project.ext.set('usesHms', false)
buildscript {
repositories {
google()
mavenCentral()
maven { url "https://developer.huawei.com/repo/" }
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.3"
classpath "com.google.gms:google-services:4.3.10"
classpath "com.huawei.agconnect:agcp:1.6.0.300"
}
}
Module level build.gradle
:
android {
...
flavorDimensions "vendor"
productFlavors {
google {
dimension "vendor"
versionNameSuffix "-google"
apply plugin: "com.google.gms.google-services"
project.ext.set('usesGms', true)
}
huawei {
dimension "vendor"
versionNameSuffix "-huawei"
apply plugin: "com.huawei.agconnect"
project.ext.set('usesHms', true)
}
}
// buildTypes should be defined after productFlavors,
// then project.ext.getProperty() can be already used
buildTypes { ... }
}
dependencies {
implementation "com.google.android.gms:play-services-base:17.6.0"
implementation "com.huawei.agconnect:agconnect-core:1.6.0.300"
implementation "com.huawei.agconnect:agconnect-crash:1.6.0.300"
implementation "com.huawei.hms:base:6.0.0.300"
...
}
With productFlavors
, dependencies could also be defined alike this (which doesn't help, because even if most is the same, the package, class and method names do not match any common API); this could only work with source sets (splitting up the below XmsActivty
into google
& huawei
):
googleImplementation "com.google.android.gms:play-services-base:17.6.0"
huaweiImplementation "com.huawei.hms:base:4.0.2.300"
In Java one can support both platforms alike this:
/**
* Abstract GMS/HMS {@link AppCompatActivity}.
*
* @author Martin Zeitler
*/
abstract class XmsActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener, OnTokenListener {
@Override
public void onResume() {
super.onResume();
if (this.isGooglePlayServicesAvailable()) {
FirebaseAuth.getInstance().addAuthStateListener(this);
} else if (this.isHuaweiMobileServicesAvailable()) {
AGConnectAuth.getInstance().addTokenListener(this);
}
}
@Override
public void onPause() {
super.onPause();
if (this.isGooglePlayServicesAvailable()) {
FirebaseAuth.getInstance().removeAuthStateListener(this);
} else if (this.isHuaweiMobileServicesAvailable()) {
AGConnectAuth.getInstance().removeTokenListener(this);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (this.isHuaweiMobileServicesAvailable()) {
AGConnectApi.getInstance().activityLifecycle().onActivityResult(requestCode, resultCode, data);
}
}
/** TODO: implement FirebaseAuth.AuthStateListener. */
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
}
/** TODO: implement OnTokenListener. */
@Override
public void onChanged(@NonNull TokenSnapshot snapshot) {
String token = snapshot.getToken();
}
/**
* It determines whether Google Mobile Services are available on the device.
* @see <a href="https://developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability">GoogleApiAvailability</a>.
* @return boolean status true, when status code is 1.
*/
boolean isGooglePlayServicesAvailable() { // 0 or com.google.android.gms.common.api.ApiException: 9: Error connecting to Google Play.
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == 0;
}
/**
* It determines whether Huawei Mobile Services are available on the device.
* @see <a href="https://developer.huawei.com/consumer/en/doc/development/HMSCore-References/huaweiapiavailability-0000001050121134">HuaweiApiAvailability</a>.
* @return boolean status true, when status code is 1.
*/
boolean isHuaweiMobileServicesAvailable() {
return HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(this) == 0;
}
}
Usage:
public class MainActivity extends XmsActivity { ... }
As it seems one can only configure one package-name per app (which in some cases makes it kind of incompatible with Firebase, unless creating two apps)... or maybe it is just organized differently? https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-config-flavor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With