With the advent of Huawei Mobile Services as a direct competitor for Google Play Services, it may be advantageous for your app to support both platforms with just one code base.
HMS Core Converter Tool
Huawei provides developers with a full set of developer tools to quickly integrate the various services from HMS Core into your app. They even went so far as to create a specific Converter Tool that allows you to grab the code of your existing app written for Google Play Services and convert it to an app that uses HMS Core. This Converter Tool also allows you to generate an app that will run on both platforms, and selects the appropriate dependencies at runtime, or create specific builds for both environments.
The Converter Tool allows you to analyse your app and determine which conversion steps can be done automatically and which ones need further attention. This of course all depends on how your current code base integrates Google Play Services.
Use your own Service Manager Factory class
If the Converter Tool is not what you want or if your app does not use Google Play Services yet, but needs to support both Google and Huawei, you can also write your own adapter that manages both services in your app.
Let's create an example where your app needs to be able to use Location Services from both Google Play and HMS.
Start by defining a ServiceManager interface that will allow your app to have a single point of entry when using either service:
public interface ServiceManager {
int SERVICE_TYPE_GMS = 1;
int SERVICE_TYPE_HMS = 2;
String TRANSPORT_GMS = "GCM";
String TRANSPORT_HMS = "HMS";
int getServiceType();
Boolean checkMobileServices();
/**
* Get or create a location manager, depending on availability
* @return
*/
@Nullable
LocationManager getLocationManager();
}
This way you can add other services later by adding specific managers per feature. For now, this will only give you a LocationManager, for which you need to define its interface:
public interface LocationManager {
int INITIAL_TRIGGER_ENTER = 1;
int INITIAL_TRIGGER_EXIT = 2;
int INITIAL_TRIGGER_DWELL = 4;
/**
* Enable location updates
*/
void enableLocationUpdates();
/**
* Disable location updates
*/
void disableLocationUpdates();
/**
* @return the last known location
*/
@Nullable
Location getLastKnownLocation();
/**
* Handle a location update, to be called from location receiver
* @param location
*/
void handleLocationUpdate(@NonNull Location location);
}
Now it's time to implement these interfaces for both platforms.
Google Play Services Service Manager
public class ServiceManagerImpl implements ServiceManager {
private static final String TAG = ServiceManagerImpl.class.getSimpleName();
private static final String LOCATION_MANAGER = "com.myapp.location.gms.LocationManagerImpl";
private Context mContext;
private LocationManager mLocationManager;
private boolean mLocationManagerAvailable = true;
public ServiceManagerImpl(@NonNull Context context) {
mContext = context;
if (!checkMobileServices()) {
throw new IllegalStateException("Google Play Services not available");
}
}
@Override
public int getServiceType() {
return SERVICE_TYPE_GMS;
}
@Override
public Boolean checkMobileServices() {
// Check that Google Play services is available
return (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mContext) == ConnectionResult.SUCCESS);
}
@Override
@Nullable
public LocationManager getLocationManager() {
if (!mLocationManagerAvailable) {
return null;
} else {
if (mLocationManager == null) {
mLocationManager = tryCreateLocationManager();
}
return mLocationManager;
}
}
private LocationManager tryCreateLocationManager() {
try {
Class<?> klass = Class.forName(LOCATION_MANAGER);
LocationManager manager =
(LocationManager) klass.getConstructor(Context.class).newInstance(mContext);
Log.d(TAG, String.format("Created %s", LOCATION_MANAGER));
return manager;
} catch (Throwable throwable) {
Log.d(TAG, "Unable to create Google Play Location Manager", throwable);
mLocationManagerAvailable = false;
return null;
}
}
}
HMS Service Manager implementation
public class ServiceManagerImpl implements ServiceManager {
private static final String TAG = ServiceManagerImpl.class.getSimpleName();
private static final String LOCATION_MANAGER = "com.myapp.location.hms.LocationManagerImpl";
private Context mContext;
private LocationManager mLocationManager;
private boolean mLocationManagerAvailable = true;
public ServiceManagerImpl(@NonNull Context context) {
mContext = context;
if (!checkMobileServices()) {
throw new IllegalStateException("HMS not available");
}
}
@Override
public int getServiceType() {
return SERVICE_TYPE_HMS;
}
@Override
public Boolean checkMobileServices() {
return (HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(mContext) == ConnectionResult.SUCCESS);
}
@Override
public LocationManager getLocationManager() {
if (!mLocationManagerAvailable) {
return null;
} else {
if (mLocationManager == null) {
mLocationManager = tryCreateLocationManager();
}
return mLocationManager;
}
}
private LocationManager tryCreateLocationManager() {
try {
Class<?> klass = Class.forName(LOCATION_MANAGER);
LocationManager manager =
(LocationManager) klass.getConstructor(Context.class).newInstance(mContext);
Log.d(TAG, String.format("Created %s", LOCATION_MANAGER));
return manager;
} catch (Throwable throwable) {
Log.d(TAG, "Unable to create HMS Location Manager", throwable);
mLocationManagerAvailable = false;
return null;
}
}
}
Google Play Location Manager implementation
public class LocationManagerImpl implements LocationManager {
private static final String TAG = LocationManagerImpl.class.getSimpleName();
private Context mContext;
private FusedLocationProviderClient mFusedLocationProviderClient;
private boolean mLocationUpdatesStarted;
private PendingIntent mLocationPendingIntent;
private Location mLastKnownLocation;
private Location mLastUpdatedLocation;
public LocationManagerImpl(@NonNull Context context) {
mContext = context;
// Setup location updates intent
Intent locationIntent = new Intent(mContext, LocationReceiver.class);
locationIntent.setAction(MyApp.INTENT_ACTION_LOCATION_UPDATED);
mLocationPendingIntent = PendingIntent.getBroadcast(mContext, 0, locationIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void enableLocationUpdates() {
if (mFusedLocationProviderClient == null) {
Log.i(TAG, "Enabling location updates");
Log.i(TAG, "start new fused location client");
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(mContext);
Log.i(TAG, "start new geofencing client");
mGeofencingClient = LocationServices.getGeofencingClient(mContext);
}
startLocationUpdates();
}
@Override
public void disableLocationUpdates() {
Log.i(TAG, "Disabling location updates");
if (mLocationUpdatesStarted) {
stopLocationUpdates();
}
mGeocoder = null;
}
@SuppressWarnings({"MissingPermission"})
private void startLocationUpdates() {
Log.i(TAG, "Starting location updates");
if (mFusedLocationProviderClient != null && !mLocationUpdatesStarted) {
Log.i(TAG, "Getting initial location");
mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(currentLocation -> {
if (currentLocation != null) {
handleLocationUpdate(currentLocation);
} else {
Log.w(TAG, "no location found yet");
}
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
mFusedLocationProviderClient.requestLocationUpdates(locationRequest, mLocationPendingIntent).addOnSuccessListener(aVoid -> {
Log.i(TAG, "location updates started");
mLocationUpdatesStarted = true;
}).addOnFailureListener(e -> {
Log.w(TAG, "location updates could not be started: " + e.getMessage());
});
});
}
}
private void stopLocationUpdates() {
Log.i(TAG, "Stopping location updates");
if (mFusedLocationProviderClient != null) {
mFusedLocationProviderClient.removeLocationUpdates(mLocationPendingIntent);
}
mLocationUpdatesStarted = false;
}
@Override
public Location getLastKnownLocation() {
return mLastKnownLocation;
}
@Override
public void handleLocationUpdate(Location location) {
Log.i(TAG, "location update received, accuracy is " + location.getAccuracy());
Log.d(TAG, "new location is " + location.getLatitude() + "," + location.getLongitude());
if (location.getLatitude() > 90.0 || location.getLatitude() < -90.0 || location.getLongitude() > 180.0 || location.getLongitude() < -180.0) {
Log.w(TAG, "received an invalid location " + location.getLatitude() + "," + location.getLongitude());
} else {
mLastKnownLocation = location;
}
}
}
public class LocationReceiver extends BroadcastReceiver {
private static final String TAG = LocationReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG, "onReceive: " + action);
if (action != null) {
switch (action) {
case MyApp.INTENT_ACTION_LOCATION_UPDATED:
if (LocationResult.hasResult(intent)) {
LocationResult result = LocationResult.extractResult(intent);
Location location = result.getLastLocation();
if (location != null) {
onLocationUpdateReceived(location);
}
}
break;
}
}
}
public void onLocationUpdateReceived(Location location) {
ServiceManager serviceManager = ServiceManagerFactory.getServiceManager();
if (serviceManager != null && serviceManager.getLocationManager() != null) {
serviceManager.getLocationManager().handleLocationUpdate(location);
}
}
}
HMS Location Manager implementation
public class LocationManagerImpl implements LocationManager {
private static final String TAG = LocationManagerImpl.class.getSimpleName();
private Context mContext;
private FusedLocationProviderClient mFusedLocationProviderClient;
private boolean mLocationUpdatesStarted;
private PendingIntent mLocationPendingIntent;
private Location mLastKnownLocation;
private Location mLastUpdatedLocation;
@Keep
public LocationManagerImpl(@NonNull Context context) {
mContext = context;
// Setup location updates intent
Intent locationIntent = new Intent(mContext, LocationReceiver.class);
locationIntent.setAction(MyApp.INTENT_ACTION_LOCATION_UPDATED);
mLocationPendingIntent = PendingIntent.getBroadcast(mContext, 0, locationIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void enableLocationUpdates() {
if (mFusedLocationProviderClient == null) {
Log.i(TAG, "Enabling location updates");
Log.i(TAG, "start new fused location client");
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(mContext);
}
startLocationUpdates();
}
@Override
public void disableLocationUpdates() {
Log.i(TAG, "Disabling location updates");
if (mLocationUpdatesStarted) {
stopLocationUpdates();
}
mGeocoder = null;
}
private void startLocationUpdates() {
Log.i(TAG, "Starting location updates");
if (mFusedLocationProviderClient != null && !mLocationUpdatesStarted) {
Log.i(TAG, "Getting initial location");
mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(currentLocation -> {
if (currentLocation != null) {
handleLocationUpdate(currentLocation);
} else {
Log.w(TAG, "no location found yet");
}
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
mFusedLocationProviderClient.requestLocationUpdates(locationRequest, mLocationPendingIntent).addOnSuccessListener(aVoid -> {
Log.i(TAG, "location updates started");
mLocationUpdatesStarted = true;
}).addOnFailureListener(e -> {
Log.w(TAG, "location updates could not be started: " + e.getMessage());
});
});
}
}
private void stopLocationUpdates() {
Log.i(TAG, "Stopping location updates");
mLastKnownLocation = null;
if (mFusedLocationProviderClient != null) {
mFusedLocationProviderClient.removeLocationUpdates(mLocationPendingIntent);
}
mLocationUpdatesStarted = false;
}
@Override
public Location getLastKnownLocation() {
return mLastKnownLocation;
}
public Task<Location> getCurrentLocation() {
if (mFusedLocationProviderClient != null) {
return mFusedLocationProviderClient.getLastLocation();
} else {
return null;
}
}
@Override
public void handleLocationUpdate(@NonNull Location location) {
Log.i(TAG, "location update received, accuracy is " + location.getAccuracy());
Log.d(TAG, "new location is " + location.getLatitude() + "," + location.getLongitude());
if (location.getLatitude() > 90.0 || location.getLatitude() < -90.0 || location.getLongitude() > 180.0 || location.getLongitude() < -180.0) {
Log.w(TAG, "received an invalid location " + location.getLatitude() + "," + location.getLongitude());
} else {
mLastKnownLocation = location;
}
}
}
public class LocationReceiver extends BroadcastReceiver {
private static final String TAG = LocationReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG, "onReceive: " + action);
if (action != null) {
switch (action) {
case MyApp.INTENT_ACTION_LOCATION_UPDATED:
if (LocationResult.hasResult(intent)) {
LocationResult result = LocationResult.extractResult(intent);
Location location = result.getLastLocation();
if (location != null) {
onLocationUpdateReceived(location);
}
}
break;
}
}
}
public void onLocationUpdateReceived(Location location) {
ServiceManager serviceManager = ServiceManagerFactory.getServiceManager();
if (serviceManager != null && serviceManager.getLocationManager() != null) {
serviceManager.getLocationManager().handleLocationUpdate(location);
}
}
}
Use a factory to create the correct Service Manager
Now every time your code needs access to (in this example) the Location Manager, it calls the Service Manager for the respective platform the app is running on. An easy way to do this is to create a factory that is a singleton:
public class ServiceManagerFactory {
private static final String TAG = ServiceManagerFactory.class.getSimpleName();
private static final String GMS_MANAGER = "com.myapp.service.gms.ServiceManagerImpl";
private static final String HMS_MANAGER = "com.myapp.service.hms.ServiceManagerImpl";
private static final Object lock = new Object();
private static ServiceManager instance;
/**
* Singleton
* @return singleton instance of ServiceManager
*/
public static ServiceManager getServiceManager(@NonNull Context context) {
synchronized (lock) {
if (instance == null) {
instance = createServiceManager(context);
}
return instance;
}
}
public static ServiceManager createServiceManager(@NonNull Context context) {
ServiceManager manager;
manager = tryCreateHmsManager(context);
if (manager == null) {
Log.w(TAG, "HMS not found, trying Google Play Services");
manager = tryCreateGmsManager(context);
}
if (manager == null) {
Log.w(TAG, "no HMS or Google Play Services found");
}
return manager;
}
@Nullable
private static ServiceManager tryCreateGmsManager(@NonNull Context context) {
try {
Class<?> klass = Class.forName(GMS_MANAGER);
ServiceManager manager =
(ServiceManager) klass.getConstructor(Context.class).newInstance(context);
Log.d(TAG, String.format("Created %s", GMS_MANAGER));
return manager;
} catch (Throwable throwable) {
Log.d(TAG, "Unable to create Google Play Service Manager", throwable);
return null;
}
}
@Nullable
private static ServiceManager tryCreateHmsManager(@NonNull Context context) {
try {
Class<?> klass = Class.forName(HMS_MANAGER);
ServiceManager manager =
(ServiceManager) klass.getConstructor(Context.class).newInstance(context);
Log.d(TAG, String.format("Created %s", HMS_MANAGER));
return manager;
} catch (Throwable throwable) {
Log.d(TAG, "Unable to create HMS Service Manager", throwable);
return null;
}
}
}
To get your Service Manager, or, for that matter, your Location Manager, just call the following from anywhere in your code:
ServiceManager sm = ServiceManagerFactory.getServiceManager(context);
if (sm != null) {
LocationManager lm = sm.getLocationManager();
}
Conclusion
As you can see, there are several ways to integrate different service platforms in your app without the need for writing specific apps for both. Give the HMS Core Converter Tool a try or use the above code as an example to build your own abstraction layer.
If your app needs a ready-to-go integration of Push Notifications, Location Services, Beacons, Scannables and more, the Notificare SDK gives you the opportunity to tap into these features on both HMS and Google Play while unlocking all the power of our platform in your app.
If you are interested in using Notificare, or have any questions about cross-platform app development like described in this article, don't hesitate to contact us, as always, via our Support Channel.
It is also worthwhile to mention that if your app already has a substantial install base on Google Play, Huawei can offer you support in getting your app in the Huawei AppGallery quickly. Don't hesitate to contact us, if you want us to get you in touch with the Huawei team.