More information like this, you can visit HUAWEI Developer Forum
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201333021967420023&fid=0101187876626530001
HMS DEVELOPMENT
Create a Beautiful Site Tracker App with Huawei Mobile Services (HMS)
Let the friendly war begins.
Hello to you all. As you know, there is an ongoing legal battle between Google i.e USA and Huawei. And things are getting serious in the tech field as well. As you may have heard, Huawei stopped using Google Mobile Services (GMS) from a while on their mobile devices and developed its own Mobile Services which known as Huawei Mobile Services (HMS). It has all the abilities that GMS has except some minor features but those would be implemented in the near future thanks to its robust roadmap of Huawei Core team. And plus, you would get the unique features that only supported by the Huawei ecosystem and its devices. If you have GMS experience somehow, the implementation of HMS would be a piece of cake for you due to its API design is identical to GMS.
Actually, you might want to use HMS Core Toolkit Plugin for those who have GMS implementations on their apps and implement HMS with less effort and pretty UI ?. It has a Convertor feature that automatically changes all GMS package name occurrences with correct HMS mappings. This little Android Studio plugin would save you time rather than some manual labor. And also, it offers a couple of features that would make your HMS development more convenient. But this is up to you.
The number of Huawei Mobile Services aka HMS samples and articles are getting bigger and bigger each day goes by. You could find numerous samples easily and implement them into your app without effort. Different HMS articles release almost in a day such as JavaScript libraries so you will have a lot of options to choose from . So enough chit chat, let’s get into the business.
In this article, I’m gonna show you how to build a site tracker app using Huawei Mobile Services (HMS). Much precisely tracker app for health institutions who do Coronavirus testing in Turkey. For that, we‘re going to use the list of institutions provided by the Minister of Health of Turkey’s website. I named the app COVID19HIT. I know, it does not sound nice but it stands for COVID-19 Health Institutions Tracker. That was the best and simple naming I could get for an app like this one. Apparently, I’m not good at naming apps . Anyways, Before getting into the development, I would like to clarify things beforehand such as the app‘s aim, capabilities, and limitations. I would leave you with concise text that I used in my README file to answer those questions.
It’s an Android sample application that uses Huawei Mobile Services (HMS) to display and search health institutions around you that supports Coronavirus testing in Turkey. The default search radius is 10 km.
The project aims to how to use HMS in real-life applications. Our use-case is displaying all the near health institutions around you that have a certificate to test for Coronavirus. The list itself declared on the website of the Ministry of Health of Turkey. You can check the list out at the link below.
The official list of health institutions
We’re going to use Analytics + Map + Location + Site Kits and Directions API to demonstrate their usage in this use-case. The application’s architecture pattern is MVVM with modular project architecture. Currently, the project has 3 modules which are App, Base and Network modules. Their usage and role differ as modules. And lastly, it developed with everyone’s favorite Kotlin Coroutine which is the language level supported feature.
After that huge boring explanation, I’ll give you short brief information about which Kits solve which use-cases for this app. And, you could click the kit name and see what is more they offer.
Analytics Kit sends user events or properties via Analytics Kit
Map Kit provides a visual map to discover what is around you and drawing 2D shapes
Location Kit locates the user’s current location
Site Kit shows nearby health institutions around 10 KM
Directions API gets you directions based on the options that you select which are by walk, by a drive, and by bike options
Development
FYI, for the sake of the article’s goal, I would only focus on the HMS Development parts in the project. And also, you could ask me any other questions that you have in mind in the comments below.
Each HMS Integration requires the same initial steps, to begin with. You could use this link to prepare your app before implementing features into it. Please, don’t skip this part. This is a mandatory phase. HMS Kits will not work as they should be without it.
After you finish your project’s initial steps, we’re ready to rock and roll.
My DI choice for this project would be Koin which I really recommend who codes in Kotlin. It really takes away all of the boilerplate jobs that Dagger brings on the table. Therefore, we start with Koin initialization in our custom application class’s onCreate() method.
Code:
startKoin {
androidLogger()
androidContext([email protected])
modules(appModule + networkModule)
}
Our module dependencies are in the app module’s com.yektasarioglu.covid19hit.di package.
Code:
val appModule = module {
// Data Sources
single { DiskHealthInstitutionDataSource() }
single { RemoteHealthInstitutionDataSource() }
// Repositories
single { HealthInstitutionRepository(get(), get()) }
viewModel { HomeViewModel(get(), get()) }
}
val networkModule = module {
// Data Sources
single { RemoteDirectionDataSource(get()) }
// Repositories
single { DirectionsRepository(get()) }
single {
OkHttpClient().newBuilder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
})
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.readTimeout(10000, TimeUnit.MILLISECONDS)
.build()
}
// Services
single {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://mapapi.cloud.huawei.com/mapApi/")
.client(get())
.build()
retrofit.create(DirectionsService::class.java)
}
}
This app has only two screens Home and Splash. Our entire logic underlies in Home screen. So, we would focus there mostly. Firstly, Map Kit has two ways to display a map i.e container in our app. The first one is using MapView in our XML which I did. Secondly, you could develop with MapFragment way too. This way of using prevents to call onStart(), onStop(), onResume(), onPause(), onDestroy(), onLowMemory(), and onSaveInstanceState(Bundle outState) methods in its Activity due to Fragment is tied to its parent Activity’s lifecycle. But I chose to use MapView rather than MapFragment. So I did call those Map Kit’s lifecycle methods in HomeActivity. You could choose whatever you want based on your needs. Then, we initialize our managers and request to get location updates at HomeActivity’s onCreate() via calling initialize() method of our ViewModel which takes Activity as a parameter.
Code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initializeUIElements()
with (viewModel) {
initialize([email protected])
initializeStyle(theme = currentTheme)
isCoordinateAvailable.observe([email protected], Observer {
viewModel.moveCameraToCurrentLocation()
viewModel.drawCirclePivotalToCurrentLocation()
})
nearbyHealthInstitutionSites.observe([email protected], Observer {
Log.i("HomeActivity", "nearbyHealthInstitutionSites -> $it")
it.forEach { site->
Log.i(TAG, "site's lat and long: ${site.location.lat}, ${site.location.lng}")
viewModel.markTheSite(site = site, distanceText = resources.getString(R.string.distance))
}
viewModel.setOnMarkerClickListener { marker ->
toast("Clicked ${marker.title}")
with (binding.actionsMenu) {
root.visibility = View.VISIBLE
}
}
toast(getString(R.string.scroll_to_see_more))
})
}
mapView = binding.mapView
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(viewModel.getOnMapReadyCallback())
}
As you would notice, we use ViewModel’s OnMapReadyCallback implementation rather than directly implementing it to Activity. In this case, our ViewModel provides the implementation somewhere else. You would see why we do that just below for a couple of reasons.
Code:
fun initialize(activity: Activity) {
initializeManagers(activity)
requestLocationUpdates()
}
We are going to use Manager classes to encapsulate HMS kit implementations and call it in ViewModel. For our previous example above, ViewModel would only call the MapKitManager’s implementation of OnMapReadyCallback. This helps us to hide all the HMS implementation details in its class, enables us to make changes easily and prevents to propagate more code in HomeViewModel. This approach also suits SOLID principles, especially the Single-responsibility principle. I strongly recommend you to learn it if you don’t know what those are.
Code:
private fun initializeManagers(activity: Activity) {
analyticsManager = AnalyticsManager(activity)
locationKitManager = LocationKitManager(activity)
mapKitManager = MapKitManager()
siteKitManager = SiteKitManager(activity, BuildConfig.HMS_API_KEY)
// For testing purposes
analyticsManager?.sendEvent("XX", Bundle().apply {
putString("TestProperty1", "TestValue1")
})
}
If you run the APK. You would see that the app has two different themes which are Light(Default) and Dark. I wanted to display different map styling for Dark Theme. This custom styling feature is supported by Map Kit. You could use this reference to make your own map style. Even, there is handy Huawei style tool that generates JSON style file based on your customization choices.
Code:
private fun requestLocationUpdates() {
locationKitManager?.requestLocationUpdatesWithCallback(
locationRequest = LocationRequest().apply {
interval = 10000L
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
needAddress = true // This let you to reach the current address information.
},
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
if (locationResult != null) {
with(locationResult.lastHWLocation) {
if (!isNearbyHealthInstitutionsFetched.get()) {
if (userLocation?.city == null &&
userLocation?.country == null &&
userLocation?.state == null &&
userLocation?.coordinate == null
) {
userLocation?.city = city
userLocation?.country = countryName
userLocation?.state = state
userLocation?.coordinate = latitude to longitude
isCoordinateAvailable.value = Unit
viewModelScope.launch {
nearbyHealthInstitutionSites.value =
withContext(context = Dispatchers.Default) {
var list = listOf<Site>()
while (list.isEmpty()) {
Log.i(MTAG, "list is empty")
list = getNearbyHealthInstitutions()
}
isNearbyHealthInstitutionsFetched.set(true)
list
} as ArrayList<Site>
}
}
}
if (stepList != null)
rotateCameraToCurrentDirection(locationResult.lastLocation, stepList!!)
}
} else Log.i(MTAG, "locationResult is NULL !!")
}
})
}
requestLocationUpdatesWithCallback() method’s callback result initiates our flow. Whenever the callback first time returns, we fetch our nearby health institutions with filtered fashion.
Code:
private suspend fun getNearbyHealthInstitutions(radius: Meter = DEFAULT_KM_RADIUS): List<Site> {
return CoroutineScope(Dispatchers.IO).async {
val list = getHealthInstitutions()
val filteredList = list?.filter { it.city == userLocation?.state?.toUpperCase() }
val result = suspendCoroutine<List<Site>> { continuation ->
addNearbyHospitals(
radius = radius,
onEnd = { nearbyHealthInstitutions ->
compareHealthInstitutionLists(
officialHealthInstitutions = filteredList!!,
nearbyHealthInstitutions = nearbyHealthInstitutions
).let { found -> continuation.resume(found) }
})
}
[email protected] result
}.await()
}
Code:
getNearbyHealthInstitutions() does all the work. In a basic sense;
Get health institutions
Filter only the exact matching ones with user location’s city
Search nearby hospitals with the help of Site Kit
Compare the filtered list with the result of Site Kit
Code:
private inline fun addNearbyHospitals(radius: Float, crossinline onEnd: (nearbyHealthInstitutions: List<Site>) -> Unit) {
if (siteKitManager == null) return
val nearbyHealthInstitutions = mutableListOf<Site>()
for (i in 1..SiteKitManager.MAX_PAGE_INDEX) {
Log.i(TAG, "i is $i")
if (siteKitManager!!.isInTheRangeOfMaxResult(pageIndex = i, pageSize = 20))
siteKitManager?.searchNearby(
location = Coordinate(
userLocation?.coordinate?.first!!,
userLocation?.coordinate?.second!!
),
radius = radius,
searchLanguage = "en",
locationType = LocationType.HOSPITAL,
pageFilters = i to 20,
searchResultListener = object : SearchResultListener<NearbySearchResponse?> {
override fun onSearchResult(results: NearbySearchResponse?) {
Log.i("TAG", "Total result count is ${results?.totalCount}")
val sites = results!!.sites
if (results.totalCount <= 0 || sites == null || sites.size <= 0)
return
for (site in sites) {
Log.i("TAG", "siteId: ${site.siteId}, name: ${site.name}, distance: ${site.distance} address: ${site.address} \r\n")
}
nearbyHealthInstitutions.addAll(sites)
if (nearbyHealthInstitutions.size == SiteKitManager.MAX_RESULT)
onEnd(nearbyHealthInstitutions)
}
override fun onSearchError(status: SearchStatus) {
Log.i("TAG", "Error : " + status.errorCode + " " + status.errorMessage)
}
}
)
}
}
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
addNearbyHospitals() returns its result to onEnd(List<Site>) lambda parameter does the post-process after its call. This enable us to get the max result which is 60. We loop until the nearbyHealthInstitutions’ element count is 60. Checking of pageIndex x pageSize ≤ 60 formula under the isInTheRangeOfMaxResult(Int, Int) method.
Then, we compare the nearby hospital list with official health institutions. This comparison’s result is our final list that we would mark it on the map. A comparison algorithm is just a basic token comparison. It uses the Levenshtein distance method to compare two strings. Nowadays, you don’t have to be a math genius to use these methods thanks to StackOverflow. But as an enthusiast, you might go and learn how the Levenshtein distance algorithm works in the first place. Before getting into the next phase, I would like to mention about data fetching layer. Under the hood, the data fetching mechanism designed to get data from a Repository. A repository method returns a result if the data wasn’t cached. Repository Pattern is another good old design pattern that you should learn if you didn’t know. It simplifies the data fetching layer of your app. You should check this link out if you’re interested in. Data collection of the app is not so robust due to the Ministry of Health of Turkey changes its HTML so often . As you would guess, all we do is some basic web scraping. As you were reading, the Ministry of Health of Turkey might change its website’s HTML some of two. And that would lead to corrupt scraped data and cause you to see fewer health institutions or nothing at all. Unfortunately, there is no REST API for getting certified health institutions in Turkey. That is all we have for now I am afraid. If you find a stable REST API for getting certified health institutions in X country. The country does not matter as long as the data is stable, just let me know. Or you could implement it on your own and contribute it to the sample project. I would gladly accept your contribution to the project.
Our last demonstration would be how to navigate to your destination point from your current location. For that, the app only offers a simple display of directions at this moment. No fancy or complex features are supported for the sake of the simplicity of our app’s context. As you would see above, there is a compound component and each component’s action would provide 3 different routes(walking/driving/bicycling). At that point, Directions API would come to rescue from our train of thoughts about how to get directions. Then, these directions would be displayed on the map with drawing polylines till the destination point via MapKit. And that would pretty much do the job in a nutshell.
Bonus Resource
I created also Postman collections for both Directions and Matrix APIs to observe all the endpoints. All you have to do is create an empty project under my projects section in AppGallery Connect. Then use this field as your API Key like the above image to test it.
Code:
fun getRoute(`for`: RouteType, onFailed: ((errorMessage: String) -> Unit)? = null) {
val originCoordinates =
Coordinates(userLocation?.coordinate?.first!!, userLocation?.coordinate?.second!!)
val destinationCoordinates = Coordinates(
mapKitManager?.selectedMarker?.position!!.latitude,
mapKitManager?.selectedMarker?.position!!.longitude
)
val routeDirection =
RouteDirection(origin = originCoordinates, destination = destinationCoordinates)
viewModelScope.launch {
var routeResponse: RouteResponse? = null
when (`for`) {
RouteType.WALK -> {
routeResponse = directionsRepository.getWalkingRoute(routeDirection) {
onFailed?.invoke(it)
}
}
RouteType.DRIVE -> {
routeResponse = directionsRepository.getDrivingRoute(routeDirection) {
onFailed?.invoke(it)
}
}
RouteType.BICYCLE -> {
routeResponse = directionsRepository.getBicyclingRoute(routeDirection) {
onFailed?.invoke(it)
}
}
}
stepList = routeResponse?.routes?.first()?.paths?.first()?.steps
routeResponse?.let {
mapKitManager?.removePolylines()
mapKitManager?.generateRoute(it)
}
}
}
After we fill stepList variable with the response. Our code block in the onLocationResult callback method which is the below would be triggered. This code block below exists in the article as the sixth gist but in the terms of laziness, I made this as another gist. Who likes to scroll up in a long article, right ? As we know, requestLocationUpdatesWithCallback() method’s callback invokes after each x interval. That would cause a rotate camera to the current direction.
Code:
if (stepList != null)
rotateCameraToCurrentDirection(locationResult.lastLocation, stepList!!)
Github Repository
yektasarioglu/covid19hit
That is it for this article. You could search any question that comes to your mind via Huawei Developer Forum. And lastly, you could find lengthy detailed videos at Huawei Developers YouTube channel. These resources diversify learning channels and make things easy to pick and learn from a huge knowledge pool. In short, there is something for everybody in here . Stay tuned for more HMS Development resources. Thanks for reading. Be safe folks.
Nice article
What should I do if error code 6 is returned when I call APIs of Site Kit?
riteshchanchal said:
What should I do if error code 6 is returned when I call APIs of Site Kit?
Click to expand...
Click to collapse
Can you provide the screenshot or the steps you call ?
Related
Hello, since I do not have 10 posts I can not post in development related forum.
Anyway, I would like to know how Bundle truly works... On Android 2.2 (HTC Desire) I managed to notice strange behaviour - sometimes a Parcelable object is put in and out of the Bundle without the call to Parcelable.writeToParcel(Parcel,int) nor Parcelable.CREATOR.createFromParcel(Parcel).
Also all the fields of class are recreated, even the ones I want to be forgotten - like reference to Context).
So I assume sometimes Bundle use Parcelable protocol and sometimes it simply uses Reflections or even protects a part of memory disallowing it to be collected be garbage collector (Bundle i C++ class wrapper so its highly possible).
My question is: how to handle this strange behaviour?
At this moment I'm doing things like that:
Code:
public class OuterClass
{
[INDENT]protected Context mContext;
protected InnerClass mNoContextData;
protected Parcelable getParcelable()
{
[INDENT]return mNoContextData;[/INDENT]
}
protected void setParcelable(Parcelable aParcelable)
{
[INDENT]mNoContextData = (InnerClass)aParcelable;[/INDENT]
}
public static class InnerClass implements Parcelable
{
[INDENT]//somedata
InnerClass()
{
[INDENT]//data init[/INDENT]
}
//parcelable implementation[/INDENT]
}[/INDENT]
}
I found another strange behavior - android can NOT find class object using above code (InnerClass) when trying to restore object from parcelable after longer time on hold. I get BadParcelable exception all the time ;/.
Hello everybody,
I am a new developer and started with Libgdx to make a simple game (Flappy Bird) an understand everything.
My English is not the best but I could understand this tutorial (tutorial) and make my "own" Flappy Bird.
To complete the tutorial I wanted to show some ads in the app but it didn't work. I used the ads from admob.
Can someone explain me how I can show ads? I think libgdx is the problem but I don't know what I did wrong.
I found this code but it doesn't work and I am getting a few errors: (Is ist correct and am I right if I do in the Android folder a new class
called BannerExample and put this code in it?
import com.google.ads.*;
public class BannerExample extends Activity {
private AdView adView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Create the adView
adView = new AdView(this, AdSize.BANNER, MY_AD_UNIT_ID);
// Lookup your LinearLayout assuming it's been given
// the attribute android:id="@+id/mainLayout"
LinearLayout layout = (LinearLayout)findViewById(R.id.mainLayout);
// Add the adView to it
layout.addView(adView);
// Initiate a generic request to load it with an ad
adView.loadAd(new AdRequest());
}
@Override
public void onDestroy() {
if (adView != null) {
adView.destroy();
}
super.onDestroy();
}
}
Click to expand...
Click to collapse
PS: Sorry for my bad English!
libGDX and AdMob
Hi,
which error messages do you get?
Which Version of libGDX do you use?
I'm not sure if it's possible make it with a class like your BannerExample.
For my app I put all the code for the ad in the AndroidLauncher class.
Additionally you should update to Google Mobile Ads because Google will stop the support of the old standalone sdk.
I think this will help you:
The Article "Google Mobile Ads in Libgdx (replaces deprecated AdMob)" in the libGDX Wiki on github.
And a sample code from "TheInvader360" on github.
I hope you find the right sites. I can't post links .
Good luck
I',m trying to read heart rate from MIO FUSE device. when connected via the app, the Mio Go App does not sync data with Google fit. I tried directly pairing the device via bluetooth menu and enabled 'Body Sensor' from google fit app.
Code:
Fitness.SensorsApi.findDataSources(mGoogleApiClient, dataSourceRequest)
.setResultCallback(dataSourcesResultCallback);
returns a data source with name of the device and type "DataType.TYPE_HEART_RATE_BPM" datasource. I use that data source to register a listener to get the data. I used the following code for that.
Code:
SensorRequest request = new SensorRequest.Builder()
.setDataSource( dataSource )
.setDataType( dataType )
.setSamplingRate( 10, TimeUnit.SECONDS )
.build();
Fitness.SensorsApi.add(mGoogleApiClient, request, this)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.e(TAG, "SensorApi successfully added");
} else {
Log.e(TAG, "adding status: " + status.getStatusMessage());
}
}
});
This method successfully executes and I get the success result too. But I did not get any callback to
Code:
@Override
public void onDataPoint(DataPoint dataPoint) {
//do stuff
}
Is there anything more I have do before getting the values.
Phone : MOTO X Running on Android M Bluetooth device : Mio Fuse
1) is there an issue on the way I try to get the values?
2) is there any other way (an app) where I can register the device which will push periodical heart rate results to google fit. I tried this app Heart Rate OS2 app but it did not even asked me to pair my device with it. Endomonodo and Sport gear tracker reported the heart rate along with activity but not directly.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
IntroductionIf you are not coming to this article from the Part 1 of the series, you can read here. This is the second part of our HMS Unity Plugin 2.0 integration guide. As you know, I wanted to talk about a part of GameService here because it requires a bit more work, not because of the plugin but because of its intrinsic nature. Adding products, managing behaviors, configuring achievements etc. take a bit more time in the AGC side. I will try to give as many details as I can give in this article; but since some of the topics are not directly related with the plugin, you may further research on how to do the tasks that I do not extensively talk about here.
I will be talking about just the Achievements parts of the GameService. However, it also has the capabilities of SaveGame and Leaderboard. You can read more about them in other articles and believe me, they are as easy to integrate as the ones I talk about, thanks to the HMS Unity Plugin 2.0.
This article also assumes that you have completed the steps in Part 1, at least the ones that are essential. GameService is already dependent on Account Kit you must check Account Kit as well from the Kit Settings menu, even if you will not use it directly in your game. (Plugin should automatically tick it for you once you tick GameService)
You do not have to integrate other kits to integrate these two kits, but some AGC side requirements are standard for all kits. I will talk about the specific parts that are about GameService here and IAP (In-app purchases) in part 3.
Without further ado, let’s get started.
GameServiceAfter enabling GameService in HMS Settings (aka Kit Settings) menu, a “Game Service” tab will be automatically added to the settings menu, as can be seen below in the screenshots. Now, I walk you through GameService step by step for those who want a bit of additional information.
Sign-In Function ImplementationAs I have warned in part 1, for the use of GameService, sign-in is required. This must be either done through Account Kit by yourself, or through GameService.
The easiest way to the do this is to just to check the box at the bottom of GameService tab. When you tick “Initialize On Start”, whenever users start your game, your game will try to log the user in immediately and they will see the “Welcome *username*” greeting message immediately if they logged in at least once in your game.
In the first-ever login in your game, they will be directed to the Huawei login page automatically, which will be done at the very first opening of your app. If you choose to do this, you do not even have to implement Account Kit. That’s it, login is done and you are ready to continue with just one click.
If you opt for not checking the box because this is not a desirable in-app behavior for you, then you must initialize the GameService manually and use the Account Kit in your own logic to log the user in.
It requires a bit of code but is not hard at all. Let’s assume that you sign-in at the Start() function of your app using Account Kit. What you have to do is to implement the SignInSuccess callback. If login is successful, success callback will be automatically executed and in there, you must initialize the GameService with just one line of code.
void Start()
{
HMSAccountManager.Instance.SignIn(); //sign the user in HMSAccountManager.Instance.OnSignInSuccess = OnSignInSuccess;
//implement callback on Start()
}
private void OnSignInSuccess(AuthAccount obj)
{
HMSGameManager.Instance.Init();
}
That’s it for the manual control. Now, you control where you want to sign your users in and also initialize the GameManager so that you can use Achievements, Leaderboards and SaveGame features.
I suggest that whichever way you choose, you do this at the first scene of the app (like a main menu etc.) and not inside the game itself, so users will not be bothered by sign-in process in-game.
AchievementsI want to add achievements to my app so when the user has done certain actions, I will reward them by unlocking some achievements. There are mainly two actions required to be done by you, the developer: First, add achievements to your app in AGC and get their ID. And second, enter the IDs to Achievements part of the plugin and implement in-game logic. That means, you need to determine where you will grant your users an achievement in your game. What kind of actions are needed to be carried out to get them?
In my case, this process is very simple. I have “Beginner, Medium and Master Scorer” achievements defined and I grant them whenever the user completes a certain score in my game. Since my game is very simple, the score is the utmost indicator of a “skilled” player, so I thought, why not?
First, let’s go to AGC (AppGallery Connect) together to add some achievements. You can go to AGC by using this link. Sign in to your developer account, click “My apps” and choose your game from the list. You will be directed to “Distribute” tab. From the left-upper bar, choose “Operate” tab instead. There, you will have “Product Management” tab opened at first from the left navigation menu, which I will use it for IAP later. Now, move to the Achievements tab to add some achievements to your game. Click Create on right to create an achievement.
You enter a name and a description to remember what this achievement is for. You can leave “incremental achievement” unchecked because I do not need it for this simple game. Also for the “revealed, hidden” option, what I did was to make the BeginnerScorer achievement revealed and the other two are hidden. So user will see them in achievements list but will not know what they are before achieving the previous achievement. You can configure them however you like. Make sure they are fitting to your game content, so users will try to play longer to achieve them. Also, I set the same logo for every one of them but I suggest you design different icons for each and every one of your achievements.
After you are done, it should look like something like this:
Do not release your achievements so you can test them. If you release them, they will be checked by AGC and be approved if they are proper. However, then, you cannot reset their progress even if you did not publish your game yet. Thus, to make sure that the development side works correctly, I will leave them as it is. Whenever you achieve them in your own game testing, you can just reset the progress and keep testing if you want to change something you do not like.
Now that I am done creating them, you can click “Obtain Resources” above and copy their IDs one by one. Then, paste them to our HMS Settings menu. After you copied them all, click “Create Constant Classes”, so HMS Unity Plugin can create a constant class for you.
The constant class will be called HMSAchievementConstants. Now let’s see how can I use them. I will need the “state”s of these achievements for my game implementation because I will check the states to grant the achievements one by one. Imagine a scenario where BeginnerScorer needs 15 points and MediumScorer needs 25 points to unlock. If the user surpasses 25 points in the first game, then the game would grant them consecutively in one run. This is not what I want, so I will access the achievement states and that requires Achievement objects. You do not have to use Achievement objects, you can just use the constant class to retrieve the IDs and immediately reveal and/or unlock them.
public void TakeDamage(int damageAmount)
{
//...
if (health <= 0)
{
//...
//Player is dead
losePanel.SetActive(true);
HMSAchievementsManager.Instance.GetAchievementsList();
}
}
Remember my TakeDamage function shown above. Since I will be unlocking achievements when the game is done, I will call my GetAchievementsList() function after the player dies. This function is necessary because it has several callbacks that which I will use. You should decide to call this function depending on your game logic and code structure. As I always do, I tell my structure in detail so you can project where you should put yours.
void Start()
{
//... HMSAchievementsManager.Instance.OnGetAchievementsListSuccess = OnGetAchievemenListSuccess;
HMSAchievementsManager.Instance.OnGetAchievementsListFailure = OnGetAchievementsListFailure; //optional
}
In the Start() function of wherever you will call GetAchievementsList() function, do as above. Basically, you are registering the these callbacks so when getting the achievements list is successful, the OnGetAchievemenListSuccess that you will write will be triggered. Failure callback is optional, you can track the errors and add some user warning if you like.
using System.Linq;
private void OnGetAchievemenListSuccess(IList<Achievement> achievementList)
{
//Implement your own achievement system here...
//Achievement beginnerScorer = achievementList[3]; -> Same thing as the line below
Achievement beginnerScorer = achievementList.First(ach => ach.Id == HMSAchievementConstants.BeginnerScorer); //Score of 15 is needed
Achievement mediumScorer = achievementList[4]; //Score of 25 is needed
Achievement masterScorer = achievementList[5]; //Score of 50 is needed
if (score >= 15 && beginnerScorer.State != 3)
{
HMSAchievementsManager.Instance.UnlockAchievement(beginnerScorer.Id);
//HMSAchievementsManager.Instance.UnlockAchievement(HMSAchievementConstants.BeginnerScorer); -> same as above
HMSAchievementsManager.Instance.RevealAchievement(mediumScorer.Id);
}
else if (score >= 25 && beginnerScorer.State == 3 && mediumScorer.State != 3)
{
HMSAchievementsManager.Instance.UnlockAchievement(mediumScorer.Id);
HMSAchievementsManager.Instance.RevealAchievement(masterScorer.Id);
}
else if (score >= 50 && mediumScorer.State == 3 && masterScorer.State != 3)
{
HMSAchievementsManager.Instance.UnlockAchievement(masterScorer.Id);
}
}
private void OnGetAchievementsListFailure(HMSException obj)
{
Debug.Log("OnGetAchievementsListFailure with code: " + obj.ErrorCode);
}
Let me explain the code above. It may look a bit complicated but it is not hard to understand. Since I registered to my callbacks, I need to implement them now. You need to implement the this callback yourself, so users can unlock achievements.
As I said, since I need the states, I use the objects of Achievement class. Normally, if I were not to care about the states, I would not even need them. I would just do:
HMSAchievementsManager.Instance.UnlockAchievement(HMSAchievementConstants.BeginnerScorer);
So, if you do not need states or other properties of Achievement class, you can also do the same. Your development cost is much less this way, thanks to the plugin. As you see, you do not even need to copy the long IDs to wherever you want to use them, you can just call constants class and use the IDs by the name you gave to them.
In the following part of the code, I get my achievements one by one from the callback parameter. A list already returned to me and I can pick what I want. Since I previously added 3 more achievements that I did not show you, my ordinal numbers start from 4. (you can check AGC console screenshot above)
Achievement beginnerScorer = achievementList.First(ach => ach.Id == HMSAchievementConstants.BeginnerScorer); //15 score is needed
Achievement mediumScorer = achievementList[4]; //25 score is needed
What I do is to get to the (4–1)rd index to get my beginner achievement. You can always match the indices of the achievements from the AGC console ordinals. There is also another way. If you import System.Linq, you can also use First function to get the achievements without using index numbers. Example is shown above. This is just to provide some alternatives for you.
In the rest of the code, I check the states and if they are not unlocked yet, or surely unlocked in the next step, I unlock my achievements. Since I made the other two achievements hidden, I also reveal them when the user unlocks the previous achievement. It is all under my control, so you can code your own logic however you like.
Also notice that I use the instance of HMSAchievementsManager when revealing and unlocking achievements. No further code required to call this because plugin handles the other cumbersome processes for you.
Achievements are done. You can see how I have become the master of my own game.
One little thing is left though. Users should be able to see what kind of achievements are there even if they are hidden. (It will be shown as hidden)
AppGallery already provides an interface for this, thus, if you want to implement this functionality you can just call one line function.
public void ShowAchievements()
{
HMSAchievementsManager.Instance.ShowAchievements();
}
Since I use a button click to call this function, I put the code in another local function. Depending on your requirements, you can call it directly.
Tips & Tricks
There are certain other callbacks related to the kits that you use through plugin. I did not talk about them because they were irrelevant for my use case. You can always check them with IntelliSense suggesting while coding. It should suggest available callbacks after Instance.
ConclusionI have integrated simple achievements to my game so that users could spend more time in my game. You can adjust the details I provided for your use case and devise a scenario that works for you.
I hope that this article has been helpful for you. You can always ask questions below, if you have anything unanswered in your mind.
The only remaining kit is IAP and it will talked about here, the part 3.
See you there!
References
HMS Unity Plugin 2.0 Branch (Github Page)
Documentation of every single kit in Huawei Docs (Links are present in the GitHub readme)
Checkout in forum
The emergence of AR technology has allowed us to interact with our devices in a new and unexpected way. With regard to smart device development, from PCs to mobile phones and beyond, the process has been dramatically simplified. Interactions have been streamlined to the point where only slides and taps are required, and even children as young as 2 or 3 can use devices.
Rather than having to rely on tools like keyboards, mouse devices, and touchscreens, we can now control devices in a refreshingly natural and easy way. Traditional interactions with smart devices have tended to be cumbersome and unintuitive, and there is a hunger for new engaging methods, particularly among young people. Many developers have taken heed of this, building practical but exhilarating AR features into their apps. For example, during live streams, or when shooting videos or images, AR-based apps allow users to add stickers and special effects with newfound ease, simply by striking a pose; in smart home scenarios, users can use specific gestures to turn smart home appliances on and off, or switch settings, all without any screen operations required; or when dancing using a video game console, the dancer can raise a palm to pause or resume the game at any time, or swipe left or right to switch between settings, without having to touch the console itself.
So what is the technology behind these groundbreaking interactions between human and devices?
HMS Core AR Engine is a preferred choice among AR app developers. Its SDK provides AR-based capabilities that streamline the development process. This SDK is able to recognize specific gestures with a high level of accuracy, output the recognition result, and provide the screen coordinates of the palm detection box, and both the left and right hands can be recognized. However, it is important to note that when there are multiple hands within an image, only the recognition results and coordinates from the hand that has been most clearly captured, with the highest degree of confidence, will be sent back to your app. You can switch freely between the front and rear cameras during the recognition.
Gesture recognition allows you to place virtual objects in the user's hand, and trigger certain statuses based on the changes to the hand gestures, providing a wealth of fun interactions within your AR app.
The hand skeleton tracking capability works by detecting and tracking the positions and postures of up to 21 hand joints in real time, and generating true-to-life hand skeleton models with attributes like fingertip endpoints and palm orientation, as well as the hand skeleton itself.
AR Engine detects the hand skeleton in a precise manner, allowing your app to superimpose virtual objects on the hand with a high degree of accuracy, including on the fingertips or palm. You can also perform a greater number of precise operations on virtual hands and objects, to enrich your AR app with fun new experiences and interactions.
Getting StartedPrepare the development environment as follows:
JDK: 1.8.211 or later
Android Studio: 3.0 or later
minSdkVersion: 26 or later
targetSdkVersion: 29 (recommended)
compileSdkVersion: 29 (recommended)
Gradle version: 6.1.1 or later (recommended)
Before getting started, make sure that the AR Engine APK is installed on the device. You can download it from AppGallery. Click here to learn on which devices you can test the demo.
Note that you will need to first register as a Huawei developer and verify your identity on HUAWEI Developers. Then, you will be able to integrate the AR Engine SDK via the Maven repository in Android Studio. Check which Gradle plugin version you are using, and configure the Maven repository address according to the specific version.
App Development1. Check whether AR Engine has been installed on the current device. Your app can run properly only on devices with AR Engine installed. If it is not installed, you need to prompt the user to download and install AR Engine, for example, by redirecting the user to AppGallery. The sample code is as follows:
Code:
boolean isInstallArEngineApk =AREnginesApk.isAREngineApkReady(this);
if (!isInstallArEngineApk) {
// ConnectAppMarketActivity.class is the activity for redirecting users to AppGallery.
startActivity(new Intent(this, com.huawei.arengine.demos.common.ConnectAppMarketActivity.class));
isRemindInstall = true;
}
2. Initialize an AR scene. AR Engine supports the following five scenes: motion tracking (ARWorldTrackingConfig), face tracking (ARFaceTrackingConfig), hand recognition (ARHandTrackingConfig), human body tracking (ARBodyTrackingConfig), and image recognition(ARImageTrackingConfig).
Call ARHandTrackingConfig to initialize the hand recognition scene.
Code:
mArSession = new ARSession(context);
ARHandTrackingConfig config = new ARHandTrackingconfig(mArSession);
3. You can set the front or rear camera as follows after obtaining an ARhandTrackingconfig object.
Code:
Config.setCameraLensFacing(ARConfigBase.CameraLensFacing.FRONT);
4. After obtaining config, configure it in ArSession, and start hand recognition.
Code:
mArSession.configure(config);
mArSession.resume();
5. Initialize the HandSkeletonLineDisplay class, which draws the hand skeleton based on the coordinates of the hand skeleton points.
Code:
Class HandSkeletonLineDisplay implements HandRelatedDisplay{
// Methods used in this class are as follows:
// Initialization method.
public void init(){
}
// Method for drawing the hand skeleton. When calling this method, you need to pass the ARHand object to obtain data.
public void onDrawFrame(Collection<ARHand> hands,){
// Call the getHandskeletonArray() method to obtain the coordinates of hand skeleton points.
Float[] handSkeletons = hand.getHandskeletonArray();
// Pass handSkeletons to the method for updating data in real time.
updateHandSkeletonsData(handSkeletons);
}
// Method for updating the hand skeleton point connection data. Call this method when any frame is updated.
public void updateHandSkeletonLinesData(){
// Method for creating and initializing the data stored in the buffer object.
GLES20.glBufferData(..., mVboSize, ...);
//Update the data in the buffer object.
GLES20.glBufferSubData(..., mPointsNum, ...);
}
}
6. Initialize the HandRenderManager class, which is used to render the data obtained from AR Engine.
Code:
Public class HandRenderManager implements GLSurfaceView.Renderer{
// Set the ARSession object to obtain the latest data in the onDrawFrame method.
Public void setArSession(){
}
}
7. Initialize the onDrawFrame() method in the HandRenderManager class.
Code:
Public void onDrawFrame(){
// In this method, call methods such as setCameraTextureName() and update() to update the calculation result of ArEngine.
// Call this API when the latest data is obtained.
mSession.setCameraTextureName();
ARFrame arFrame = mSession.update();
ARCamera arCamera = arFrame.getCamera();
// Obtain the tracking result returned during hand tracking.
Collection<ARHand> hands = mSession.getAllTrackables(ARHand.class);
// Pass the obtained hands object in a loop to the method for updating gesture recognition information cyclically for processing.
For(ARHand hand : hands){
updateMessageData(hand);
}
}
8. On the HandActivity page, set a render for SurfaceView.
Code:
mSurfaceView.setRenderer(mHandRenderManager);
Setting the rendering mode.
mSurfaceView.setRenderMode(GLEurfaceView.RENDERMODE_CONTINUOUSLY);
Physical controls and gesture-based interactions come with unique advantages and disadvantages. For example, gestures are unable to provide the tactile feedback provided by keys, especially crucial for shooting games, in which pulling the trigger is an essential operation; but in simulation games and social networking, gesture-based interactions provide a high level of versatility.
Gestures are unable to replace physical controls in situations that require tactile feedback, and physical controls are unable to naturally reproduce the effects of hand movements and complex hand gestures, but there is no doubt that gestures will become indispensable to future smart device interactions.
Many somatosensory games, smart home appliances, and camera-dependent games are now using AR to offer a diverse range of smart, convenient features. Common gestures include eye movements, pinches, taps, swipes, and shakes, which users can strike without having to learn additionally. These gestures are captured and identified by mobile devices, and used to implement specific functions for users. When developing an AR-based mobile app, you will need to first enable your app to identify these gestures. AR Engine helps by dramatically streamlining the development process. Integrate the SDK to equip your app with the capability to accurately identify common user gestures, and trigger corresponding operations. Try out the toolkit for yourself, to explore a treasure trove of powerful, interesting AR features.
References
AR Engine Development Guide
AR Engine Sample Code