UrbanHome是一款提供房屋维修服务的移动应用。如有维修需求,用户可通过该应用联系所在城市的管道工,电工,保洁,漆匠,木匠,修理工等,或是搜寻导航附近的维修商店。
通过构建UrbanHome这款应用,您可以实现以下华为移动服务的功能:
1. 通过账号服务完成用户验证。
2. 通过云数据库,不同城市的维修者们能够增、删、改、查自己的信息。
3. 通过用户身份服务,用户可以管理多个地址并基于当前地址通过云数据库查看维修者信息。
4. 通过位置服务,定位服务和地图服务,用户可以搜索并导航附近的维修店。
在这个Codelab中,您将会建立一个集成了账号服务,地图服务、定位服务、位置服务、云数据库和用户身份服务的项目。在该项目中,您可以尝试:





在这个Codelab中,您将学到:
说明:需要一个华为帐号,并且此账号身份已验证。
请提前准备上述硬件环境和相关设备。
请提前准备上述软件环境。
要集成HMS Core相关服务,需要完成以下准备:
具体操作,请按照HMS Core集成准备中的详细说明来完成。
前往“项目设置”页面,选择“API管理”并开通下述服务。
说明:部分API默认是关闭的,您必须手动启用。

至此,您已成功启用应用所需的华为服务。
通过华为帐号开放服务,Android应用可以方便和安全地实现快速登录授权、读取短信验证码等功能。凭借用户授权凭证(即Access Token),应用可快速调用华为开放接口。
在依赖代码块中添加账号服务SDK的依赖。
implementation 'com.huawei.hms:hwid:5.0.3.301'
1.申请授权,获取Token。
若用户选择使用华为账号登录应用,请调用如下startAuthService()方法启动授权。
- /**
- * Start AuthService and request the scope parameters
- */
- private fun startAuthService() {
- HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
- .setUid()
- .setProfile()
- .setMobileNumber()
- .setEmail()
- .setIdToken()
- .setAccessToken()
- .setAuthorizationCode()
- .setScopeList(scopes)
- .createParams()
- startActivityForResult(service.signInIntent, AppConstants.LOGIN_AUTH_CODE)
- }
2.调用如下回调方法检测用户授权是否成功。
说明:更多详细信息请参考账号服务接入流程。
- /**
- *Check authentication is successful or not.
- */
- override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (resultCode == RESULT_OK)
- when (requestCode) {
- AppConstants.LOGIN_AUTH_CODE -> {
- AGConnectAuth.getInstance().signOut()
- val authHuaweiIdTask: Task<AuthHuaweiId> =
- HuaweiIdAuthManager.parseAuthResultFromIntent(data)
- if (authHuaweiIdTask.isSuccessful) {
- huaweiAccount = authHuaweiIdTask.result
- Log.d(
- TAG,
- AppConstants.LOGIN_GET_ACCESS_TOKEN
- )
- val credential = HwIdAuthProvider.credentialWithToken(huaweiAccount?.accessToken)
- agConnectAuth.signIn(credential)
- ?.addOnSuccessListener {
- it.user.displayName
- it.user.email
- it.user.uid
- it.user.providerInfo
- mCloudDBZoneWrapper
- .setmUiCallBack(this)
- mCloudDBZoneWrapper
- .openCloudDBZoneV2()
- }
- ?.addOnFailureListener {
- Log.e(TAG, AppConstants.LOGIN_FAILED)
- }
- } else {
- Log.e(
- TAG,
- AppConstants.LOGIN_FAILED
- )
- }
- }

地图服务给您提供一套地图开发调用的SDK,地图数据覆盖超过200个国家和地区,支持70多种地图展示与搜索语言,方便您轻松地在应用中集成地图相关的功能,全方位提升用户体验。
在本项目中,该服务用于在地图上展示用户当前位置和修理店位置并为用户规划路线。
接入地图服务具体如下:
1.添加下述依赖集成地图服务。
implementation 'com.huawei.hms:maps:5.0.5.301'
2.初始化Map View。
- MapsInitializer.setApiKey(AppConstants.API_KEY)
- mMapView.onCreate(mapViewBundle)
- mMapView.getMapAsync(this)
3.加载Map View。
- /**
- *To load Map View
- */
- Override fun onMapReady(huaweiMap: HuaweiMap) {
- latLng1 = LatLng(Utils.curentLatitude, Utils.currentLongitude)
- latLng2 = lat?.let { lng?.let { it1 -> LatLng(it, it1) } }
- hMap = huaweiMap
- hMap?.isMyLocationEnabled = true
- val build = CameraPosition.Builder().target(latLng1).zoom(3f).tilt(45f).build()
- val cameraUpdate = CameraUpdateFactory.newCameraPosition(build)
- hMap?.apply {
- animateCamera(cameraUpdate)
- moveCamera(cameraUpdate)
- }
- addOriginMarker(latLng1!!)
- latLng2?.let { addDestinationMarker(it) }
- removePolylines()
- hMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng1, 10f))
- mMarkerOrigin?.showInfoWindow()
- hMap?.apply {
- moveCamera(CameraUpdateFactory.newLatLngZoom(latLng2, 10f))
- resetMinMaxZoomPreference()
- }
- mMarkerDestination?.showInfoWindow()
-
- }
4.在地图上展示用户/修理者位置。
说明:更多详细信息请参考地图服务接入流程。
- /**
- * This method shows a marker for Consumer location on Map.
- */
- private fun addOriginMarker(latLng: LatLng) {
- if (null != mMarkerOrigin) {
- mMarkerOrigin?.remove()
- }
- val address = getCompleteAddressString(Utils.curentLatitude, Utils.currentLongitude)
- mMarkerOrigin = hMap?.addMarker(
- MarkerOptions().position(latLng)
- .anchorMarker(0.5f, 0.9f)
- .title("Current Location")
- .snippet(address)
- )
- }

Android定位SDK采用全球卫星导航系统(Global Navigation Satellite System,简称GNSS)、Wi-Fi、基站等多途径的混合定位模式进行定位,赋予您的应用灵活的全球定位能力。目前,该服务提供融合定位、活动识别、地理围栏等主要能力。
1.添加下述依赖集成定位服务。
implementation 'com.huawei.hms:location:5.0.4.300'
2.获取用户当前位置坐标(经度和纬度)。
- /**
- *This method shows a marker for Service Provider location on HMS Map
- */
- private fun addDestinationMarker(latLng: LatLng) {
- if (null != mMarkerDestination) {
- mMarkerDestination?.remove()
- }
- mMarkerDestination = hMap?.addMarker(
- MarkerOptions().position(latLng).anchorMarker(0.5f, 0.9f).title(storeName)
- .snippet(storeAddress)
- )
- }
3.在Android manifest文件中添加位置权限。
android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
4.获取用户当前更新位置坐标(经度和纬度)。
- /**
- *This method fetches location updates
- */
- private fun startLocationUpdates() {
- fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
- }
-
- private val locationCallback = object : LocationCallback() {
- override fun onLocationResult(locationResult: LocationResult?) {
- locationResult ?: return
- for (location in locationResult.locations) {
- setLocationData(location)
- }
- }
- }
-
- /**
- * To set location data.
- */
- private fun setLocationData(location: Location) {
- value = LocationModel(longitude = location.longitude, latitude = location.latitude)
- }
5.获取最新位置信息。
说明:更多详细信息请参考定位服务接入流程。
- /**
- * To fetch last location information
- */
- override fun onActive() {
- super.onActive()
- fusedLocationClient.lastLocation
- .addOnSuccessListener { location: Location? ->
- location?.also {
- setLocationData(it)
- }
- }
- startLocationUpdates()
- }
用户身份服务为用户提供统一的地址管理服务,包括地址录入、编辑、删除和查询,支持用户一键授权应用使用地址信息,高效便利。
1.添加下述依赖集成用户身份服务。
implementation 'com.huawei.hms:identity:5.1.0.300'
2.通过用户身份服务获取用户地址信息。
- /**
- * To fetch user address from Identity kit
- */
-
- private fun getUserAddress() {
- val task = Address.getAddressClient(this@MainActivity).getUserAddress(UserAddressRequest())
- task.addOnSuccessListener {
- Log.i(TAG, AppConstants.LOGIN_USER_DATA_SUCCESS)
- try {
- startActivityForResult(it)
- } catch (ex: IntentSender.SendIntentException) {
- Log.d(TAG, "SendIntentException")
- }
- }.addOnFailureListener {
- Log.i(TAG, AppConstants.LOGIN_USER_DATA_FAILED)
- }
- }
3.在用户首次登录时调用下述回调从用户身份服务提供的地址列表中选择地址。
- /**
- * To fetch user address result.
- */
- private fun startActivityForResult(result: GetUserAddressResult) {
- val status = result.status
- if (result.returnCode == 0 && status.hasResolution()) {
- Log.i(TAG, AppConstants.LOGIN_RESULT_RES)
- status.startResolutionForResult(
- this@MainActivity, AppConstants.LOGIN_GET_ADDRESS_REQUESTCODE
- )
- } else {
- Log.i(TAG, AppConstants.LOGIN_RESULT_RES_FAILED)
- Utils.showToast(this@MainActivity, getString(R.string.msg_failed_user_resolution))
- }
- }
4.读取用户地址。
说明:更新详细信息,请参考用户身份服务接入流程。
- /**
- * To read user address result.
- */
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when(requestCode) {
- AppConstants.LOGIN_GET_ADDRESS_REQUESTCODE -> {
- onGetAddressResult(resultCode, data)
- fragmentCommunicator?.passDataToFragment(data)
- }
- }
- }

位置服务提供位置查询服务,帮助您的用户更加方便地使用位置相关服务,包括关键字搜索、周边搜索、地点详情、及地点搜索建议等,有助于您的App吸引更多用户,提升用户粘性。
1.添加下述依赖集成位置服务。
implementation 'com.huawei.hms:site:5.0.2.300'
2.初始化位置服务。
searchService = SearchServiceFactory.create(this, Utils.getApiKey())
3.通过用户身份服务获取用户地址信息。
说明:更多详细信息,请参考位置服务接入流程。
- /**
- *To fetch nearby stores based on user's current location
- */
- intent.let {
- val request = NearbySearchRequest().apply {
- queryString=it.getStringExtra(AppConstants.REQUEST_QUERY).toString()
- setQuery(queryString)
- setLocation(
- Coordinate(
- it.getDoubleExtra(AppConstants.SERVICE_LAT_KEY, 0.0),
- it.getDoubleExtra(AppConstants.SERVICE_LNG_KEY, 0.0)
- )
- )
- }
- imageString = it.getStringExtra(AppConstants.PROVIDER_IMAGE_KEY).toString()
- searchService?.nearbySearch(request, searchResultListener)
- }

在这个步骤中,您可以学到:
该云数据库十分契合本工程项目,有利于我们进行数据的增、删、查、改操作。
使用云数据库构建应用服务需完成下述准备工作:
使用云数据库前,您需要先开通服务。
1.登录AppGallery Connect,点击“我的项目”。
2.在项目列表中选择项目和需要开通云数据库的应用。
3.在导航树上选择“构建”,点击“云数据库”。
4.点击“立即开通”。

5.如果您之前没有选择数据处理位置,开通云数据库后首先将会设置数据处理位置。

6.云数据库初始化完成后,该服务成功开通。
下述示例将展示如果在AppGallery Connect上新增和导出用于Android应用开发的java格式对象类型文件。
1.登录AppGallery Connect,选择“我的项目”。
2.在项目列表页面中选择项目,单击项目下需要创建对象类型的应用。
3.在导航树上选择“构建”,点击“云数据库”。
4.点击“新增”,进入创建对象类型页面。

5.输入对象类型名为LoginInfo后,点击“下一步”。
6.点击“+新增字段”,新增如下字段后,单击“下一步”。
| 字段名称 | 类型 | 主键 | 非空 | 加密 | 默认值 |
| user_id | Integer | √ | √ | – | – |
| user_email | String | – | – | – | – |
| user_name | String | – | – | – | – |
| user_phone | Double | – | – | – | – |
| photo_uri | String | – | – | – | – |
| device_token | Date | – | – | – | – |
| shadowFlag | Boolean | – | – | – | true |
7.(可选)点击“新增索引”。
8.按照如下要求设置各角色权限后,点击“下一步”。
| 角色 | query | upsert | delete |
| 所有人 | √ | – | – |
| 认证用户 | √ | √ | √ |
| 数据创建者 | √ | √ | √ |
| 管理员 | √ | √ | √ |
9.点击“确定”。
在对象类型列表中可以看到已创建的对象类型。
重复上述步骤,完成Service type、Service Category对象类型的创建。
10.点击“导出”。

11.设置导出文件格式为java格式。
12.设置java文件类型为“android”。
13.输入java文件中的包名。
包名只能包含以下3种字符:
字母(A-Z或a-z)
数字(0-9)
特殊字符:_和.
14. 点击“导出”。
文件将会导出至本地,其内包含该版本中所有的对象类型。导出的java格式文件在后续步骤用于添加至本地开发环境。
您可基于AppGallery Connect在云侧创建数据存储区,请您遵循操作步骤创建一个存储区名称为“UrbanHomeServices”的存储区。
1.登录AppGallery Connect,点击“我的项目”。
2.在项目列表页面中选择项目,单击项目下需要创建存储区的应用。
3.在导航树上选择“构建 > 云数据库”。
4.选择“存储区”页签。
5.点击“新增”,进入创建存储区页面。

6.输入存储区名称为“UrbanHomeSevices”。
7.点击“确定”。
8.创建完成后返回存储区列表中,可以查看已创建的存储区。
1.在项目级的/app/build.gradle文件中dependencies节点添加Cloud DB SDK。
implementation 'com.huawei.agconnect:agconnect=database:1.2.3.301'
2.在build.gradle文件中设置Java源码兼容模式为JDK1.8版本。
- compileOptions {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
- }
本地应用开发无需再次创建对象类型。
1.将已在AppGallery Connect上导出的全部java格式文件添加至本地开发环境。
2.Initialize Cloud DB. 通过AGConnectCloudDB类中的createObjectType()方法实现对象类型的定义和创建。
在添加对象类型文件后,您就可以使用云数据库进行应用开发。开发应用时,需要先初始化AGConnectCloudDB,然后创建Cloud DB zone和对象类型。
1.在应用的CloudDBZoneWrapper类中初始化AGConnectCloudDB。
- /**
- * To initialize AGConnectCloudDB
- */
- public static void initAGConnectCloudDB(Context context) {
- AGConnectCloudDB.initialize(context);
- }
2.获取AGConnectCloudDB实例和创建对象类型。
- mCloudDB = AGConnectCloudDB.getInstance();
- mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());
3.创建Cloud DB zone配置对象,并打开该Cloud DB zone。
- /**
- * This method is used to open Cloud DB zone.
- */
- public void openCloudDBZoneV2() {
- mConfig = new CloudDBZoneConfig(AppConstants.URBAN_HOME_SERVICES,
- CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE, CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
- mConfig.setPersistenceEnabled(true);
- Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
- openDBZoneTask.addOnSuccessListener(cloudDBZone -> {
- Log.w(TAG, "open clouddbzone success");
- mCloudDBZone = cloudDBZone;
- // Add subscription after opening cloudDBZone success
- mUiCallBack.onInitCloud();
- addSubscription();
- }).addOnFailureListener(e ->
- Log.w(TAG, "open clouddbzone failed for"));
- }
写入数据您可以使用executeUpsert()接口向当前Cloud DB zone中写入一个或一组对象。
- /**
- * This method is used to insert data into Cloud DB.
- */
- public void insertDbZoneInfo(T objectInfo) {
- if (mCloudDBZone == null) {
- Log.w(TAG, "CloudDBZone is null, try re-open it");
- return;
- }
- Task<Integer> upsertTask = mCloudDBZone.executeUpsert(objectInfo);
- upsertTask.addOnSuccessListener(cloudDBZoneResult -> {
- mUiCallBack.onInsertSuccess(cloudDBZoneResult);
- }).addOnFailureListener(e -> {
- mUiCallBack.updateUiOnError("Insert table info failed");
- });
- }
用户在应用界面中新增的数据,将会被存储在云侧。在端侧注册数据变化监听器,当云侧数据发生变化时,端侧能够感知数据变化,及时刷新本地应用数据。
调用subscribeSnapshot()方法并设置查询条件可以指定监听对象。当监听对象的数据发生变化时,端侧会收到通知,根据快照获取数据变化信息,从云侧同步数据至端侧应用。
- /**
- * This listener is used to get snapshot
- */
- private OnSnapshotListener
mSnapshotListener = (cloudDBZoneSnapshot, e) -> { - if (e != null) {
- Log.w(TAG, "onSnapshot" );
- return;
- }
- CloudDBZoneObjectList
snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects(); - List
dbZoneList = new ArrayList<>(); - try {
- if (snapshotObjects != null) {
- while (snapshotObjects.hasNext()) {
- T objectInfo = snapshotObjects.next();
- dbZoneList.add(objectInfo);
- }
- }
- mUiCallBack.onSubscribe(dbZoneList);
- } catch (AGConnectCloudDBException snapshotException) {
- Log.w(TAG, "onSnapshot:(getObject)");
- } finally {
- cloudDBZoneSnapshot.release();
- }
- };
您可以通过executeQuery()、addOnSuccessListener()和addOnFailureListener()方法组合,实现异步方式查询数据。
- /**
- * This method is used to query all data from Cloud DB.
- */
- public void queryAllData(CloudDBZoneQuery
query ) { - if (mCloudDBZone == null) {
- Log.w(TAG, "CloudDBZone is null, try re-open it");
- return;
- }
- Task<CloudDBZoneSnapshot
> queryTask = mCloudDBZone.executeQuery(query, - CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
- queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot
>() { - @Override
- public void onSuccess(CloudDBZoneSnapshot
snapshot ) { - processQueryResult(snapshot);
- }
- }).addOnFailureListener(new OnFailureListener() {
- @Override
- public void onFailure(Exception e) {
- mUiCallBack.updateUiOnError("Query failed");
- }
- });
- }
您可以使用executeDelete()方法删除一个或一组对象。删除数据时,Cloud DB会根据传入对象主键删除相应的数据,不会比对该对象其它属性与存储的数据是否一致。删除一组对象时,删除操作是原子的,即对象列表中的对象要么全部删除成功,要么全部删除失败。
- /**
- * This method is used delete table data on Cloud DB.
- */
- public void deleteTableData(List
tableObject ) { - if (mCloudDBZone == null) {
- Log.w(TAG, "CloudDBZone is null, try re-open it");
- return;
- }
- Task<Integer> deleteTask = mCloudDBZone.executeDelete(tableObject);
- if (deleteTask.getException() != null) {
- mUiCallBack.updateUiOnError("Delete service type table failed");
- return;
- }
- mUiCallBack.onDelete(tableObject);
- }
- }
您可以使用editService()方法编辑修理工的信息。
- /**
- * This method is used to edit Service details.
- */
- override fun editService(listObject: ServiceType) {
- val intent = Intent(this, AddServiceActivity::class.java)
- intent.apply {
- putExtra(AppConstants.CATEGORY_NAME, listObject.cat_name)
- putExtra(AppConstants.PROVIDER_PH_NUM, listObject.phone_number.toString())
- putExtra(AppConstants.PROVIDER_MAIL_ID, listObject.email_id)
- putExtra(AppConstants.PROVIDER_COUNTRY, listObject.country)
- putExtra(AppConstants.PROVIDER_ID, listObject.id)
- putExtra(AppConstants.PROVIDER_NAME, listObject.service_provider_name)
- putExtra(AppConstants.PROVIDER_CITY, listObject.city)
- putExtra(AppConstants.PROVIDER_STATE, listObject.state)
- }
- startActivity(intent)
- }

提示:
说明:更多详细信息,请参考云数据库接入文档。
祝贺您,您已成功构建UrbanHome。
有关更多信息,请参阅以下官方文档。
本Codelab中的示例代码下载地址如下:源码下载
欲了解更多更全技术文章,欢迎访问https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh