I've been reading the huawei's documentation to implement their services.
The documentation is easy and clear, but I have a few doubts.
Huawei's SDK can exist in the same apk for both stores? or I need implement a different apk for gms and hms?
If I implement hms how can I know if hms is reading data from other devices like MOTO etc.
You can use both of HMS and GMS services.
Some of HMS service support to non-Huawei devices (like Scan Kit) and some of kit tighlty bound the EMUI and Huawei phones.
If you need to apply you already created applıcation moved into HMS ecosystem, you can use HMS Toolkit and quickly implemented HMS services. (details)
Huawei's SDK can exist in the same apk for both stores?
- yes, you can create one app and implement libraries for GMS and HMS.
If I implement hms how can I know if hms is reading data from other devices like MOTO etc.
You call functions from Google's or Huawei's responsible for detecting services.
Please check out my latest answer from here: https://stackoverflow.com/a/60587678/619673
There are a couple ways to handle this. Of course you can choose to maintain 2 sets of source code, which is highly not recommended, and you can choose to keep libraries from both sides, detect which service is available and call them accordingly. I would however recommend implementating different product flavours and build your product according to the platform.
android{
flavorDimensions "default"
productFlavors{
hmsVersion{
//select the dimension of flavor
dimension "default"
//Configure this flavor specific app name published in Huawei App Gallery
resValue "string", "flavored_app_name", "App name"
}
gmsVersion{
//select the dimension of flavor
dimension "default"
//Configure this flavor specific app name published in Play Store
resValue "string", "flavored_app_name", "App Name"
}
}
}
and then you can do something like this
// HMS Flavor
hmsVersionImplementation 'com.huawei.hms:hianalytics:4.0.3.300'
// GMS Flavor
gmsVersionImplementation 'com.google.firebase:firebase-analytics:17.4.0'
Related
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?
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:
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.
While it really depends on architecture of your app, there are 2 reasonable alternatives so far;
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
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/
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 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).
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 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.
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.
I have an android sdk which requires the app using it to have google-services.json file in its project directory.
I am wondering what will happen if the app already has a google-services.jsonfile from its implementation and is receiving FCMs from its own google developers console.
Can an app have multiple google-services.json file for the same flavor.
Can an app receive FCM's from two different google developers console account? or How can an app receive FCM's from two different google developers console?
I am just trying to explore ways as to how I can send FCM from my server to the app without making the app change its underlying design.
Downvoting must be accompanied with reason.
If you have multiple flavors configured in your app is intende to say that you will create multiple apps with different package name, for example:
flavorDimensions "mysite"
productFlavors {
elnorte {
applicationId 'com.jorgesys.creatorhdplayer'
manifestPlaceholders = [appName: "Creator hd player"]
dimension "mysite"
}
reforma {
applicationId 'com.jorgesys.creatorhdfree'
manifestPlaceholders = [appName: "Creator hd free"]
dimension "mysite"
}
mural {
applicationId 'com.jorgesys.creatorhd'
manifestPlaceholders = [appName: "Creator hd"]
dimension "mysite"
}
}
therefore you need to add diferent google-services.json files in your proyect for every application that needs google play services :
The google-services.json file is related to the package name so you just need one file for every application. The same if you have only one flavor configured (one application)
i have successfully made a project apk which allow the watch download the wear app. And i am trying to use that wearable code to support standalone for wear 2.0 as well - seem not much resources in the internet.
my question is how to determine if the wearable device is 1.0 or 2.0. i made use of productFlavors based on this link as follows:
android {
// Allows you to reference product flavors in your
// phone module's build.gradle file
publishNonDefault true
...
defaultConfig
{
// This is the minSdkVersion of the Wear 1.x embedded app
minSdkVersion 23
...
}
buildTypes {...}
productFlavors {
wear1 {
// Use the defaultConfig value
}
wear2 {
minSdkVersion 25
}
}
}
As i recalled, wear 1.0 usually collect data from phone and wear 2.0 has ability to access data via the internet. Please correct me if i am wrong.
So if the wearable is 1.0, it uses Wearable.API and sync with the phone. Otherwise, the wearable sync with cloud.
I had a look on this post which seems useful but i do not quite understand.
PackageManager pm = getApplicationContext().getPackageManager();
pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
Should i set a different packagename(or applicationId) for wear2 so that i can use this method?
is there any drawback when i put standalone version on play store? i suppose i have to create a new project in this way.
Please can anyone advise the best way to achieve my purpose?
If you want to distribute and maintain two separate APKs, then the build flavor is probably a reasonable way to go. But I would suggest that this won't be a good experience for either you or your users; it's more work for you, and it'll be confusing for them (which version do I install? why doesn't this app work after I my watch upgraded to Wear 2.0? and so on).
My suggestion would be to put it all in one APK, and simply choose which sync technique to use at run time:
if (Build.VERSION.SDK_INT < 24) {
// Wear 1.x
} else {
// Wear 2+
}
Is it possible to add Flavors to the new version of an already published App?
If yes, suppose the following senario:
I have a published app in Play Store (e.g. com.example.android). In the new version of the app I add (e.g. free and paid) Flavors. This addition changes the package name of the app to com.example.android.free and com.example.android.paid.
Suppose I publish only the com.example.android.free Flavor.
What will the link to the Play store be like?
https://play.google.com/store/apps/details?id=com.android.example (as it already was)
or it will be renamed to
https://play.google.com/store/apps/details?id=com.android.example.free
Just to be clear on the answer by marmor:
You CAN create flavours after you have a published application. But if you wish to keep the app published and updatable on Play Store that same app MUST have the original package name which means that it has to be one of the flavours created AND its package name left untouched.
As an example, imagine that you have one published app and then you decide that you want a FREE and a PRO version of it. You change your build.gradle file to have two flavours:
defaultConfig {
applicationId "com.mycompany.awesomeapp"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
}
flavorDimensions "feature"
productFlavors {
free{
}
pro{
applicationIdSuffix ".pro"
}
}
Now what this means is that the original app in the store is now the FREE product flavour and you can sign it and update it to the Play Store. You can even change its name. What matters is that its package name remains the same, in this case
com.mycompany.awesomeapp
which is the default defined and that uniquely identifies your app.
The PRO version is a different app now that you can publish and update as a new app. Its package name is now
com.mycompany.awesomeapp.pro
Meaning it is a new, different app, even though it shares some code base.
You can't change the package name of an already published app, you can however have two flavors free/paid, but one of them should have the original package name:
e.g.
free = com.example.android
paid = com.example.android.paid
which will mean the free app would update the currently existing one, and the paid app will be a new app on Google Play (with zero statistics and downloads).
the links to the 2 apps would be as expected:
https://play.google.com/store/apps/details?id=com.android.example
and
https://play.google.com/store/apps/details?id=com.android.example.paid
If you want to enjoy the best of both worlds, look into [in-app-billing][1] to allow your users to download the app for free, and pay within the app to unlock premium features.
I would like to offer my Android 4+ app both in the Play Store and in the Amazon Market. I found several questions here on SO dealing with the question how to integrate both stores in the same APK. This seems to be quite difficult since there is not reliable why to check wether the app was download from store A or B, but if it was loaded from B all links to the store, reviews, etc. have to point back to B, etc...
Thus both stores in the same APK is not what I am looking for. I would like to create two different APKs, one for each store. Additional bonus: The APKs will be smaller since each will only contain the libs it really needs. How can this be done?
In Xcode/iOS I would simply create two different targets, each with its own set of libs and configurations. How can this be done in Eclipse? There is only one AppManifest and the "File/Export/Export Android Application" option always uses the same configuration to create the same APK.
What can I do to create two different app version from the same Eclipse project?
not the answer you're looking for but it's the reality.
Eclipse + ADT is not a very flexible way of building apps and to do what you're asking for you'll need a fairly big amount of ANT scripting (I saw it before in a banking app that build different .apks for each of it's brands). But it's probably even more complex than building everything into one apk.
On the other hand, if you're willing to migrate your project to AndroidStudio + Gradle, that is a way more flexible approach to software building and they have an "easy to use" concept of flavours, here is a snippet of the build.gradle of the app I work:
productFlavors {
phone {
resConfigs "xhdpi", "xxhdpi", "xxxhdpi", "nodpi"
versionCode 100000 + project.ver.versionCode
}
phone_low_end {
resConfigs "ldpi", "mdpi", "hdpi", "nodpi"
versionCode 200000 + project.ver.versionCode
}
tablet {
versionCode 300000 + project.ver.versionCode
}
unified {
versionCode project.ver.versionCode
}
}
sourceSets{
unified{
res {
srcDir 'src/tablet/res'
}
assets{
srcDirs 'src/phone/assets', 'src/tablet/assets'
}
}
}
and with that fairly small configuration the project is being built with different versionCode, with different assets. For your project you could easily integrate like this:
sourceSets{
googleplay{
src {
srcDir 'src/googleplay/java/'
}
}
amazon{
src {
srcDir 'src/amazon/java/'
}
}
}