【多服務場景化解決方案】智能家居(UrbanHome)

介紹

UrbanHome是一款提供房屋維修服務的移動應用。如有維修需求,用戶可通過該應用聯繫所在城市的管道工,電工,保潔,漆匠,木匠,修理工等,或是搜尋導航附近的維修商店。

通過構建UrbanHome這款應用,您可以實現以下華為移動服務的功能:

1. 通過賬號服務完成用戶驗證。

2. 通過雲數據庫,不同城市的維修者們能夠增、刪、改、查自己的信息。

3. 通過用戶身份服務,用戶可以管理多個地址並基於當前地址通過雲數據庫查看維修者信息。

4. 通過位置服務,定位服務和地圖服務,用戶可以搜索並導航附近的維修店。

您將建立什麼

在這個Codelab中,您將會建立一個集成了賬號服務,地圖服務、定位服務、位置服務、雲數據庫和用戶身份服務的項目。在該項目中,您可以嘗試:

  • 使用賬號服務實現華為賬號登錄。

cke_755375.png

  • 使用雲數據庫管理數據。

cke_765671.png

  • 使用定位服務獲取當前位置的坐標並使用地圖服務繪製用戶前往修理店的路線。

cke_780131.png

  • 使用用戶身份服務管理地址。

cke_794742.png

  • 使用位置服務搜索附近修理店。

cke_807776.png

您將學到什麼

在這個Codelab中,您將學到:

  • 如何使用賬號服務實現華為賬號登錄。
  • 如何使用定位服務獲取用戶當前位置。
  • 如何使用地圖服務在HMS地圖上定位消費者所在位置。
  • 如何使用位置服務獲取周邊修理店位置。
  • 如何使用雲數據庫為修理者提供信息的增、刪、改、查操作。
  • 如何使用用戶身份服務為用戶提供地址的增、刪、改、查操作。

 

您需要什麼

說明:需要一個華為帳號,並且此賬號身份已驗證。

硬件要求

請提前準備上述硬件環境和相關設備。

  • 運行Windows 10操作系統的台式機或筆記本電腦。
  • 安裝HMS Core (APK) 5.1.0.309或以上版本的華為手機一部。

軟件要求

請提前準備上述軟件環境。

  • Android Studio 4.X
  • JDK 1.8或以上。
  • SDK Platform 28或以上。
  • Gradle 4.6或以上。

能力接入準備

要集成HMS Core相關服務,需要完成以下準備:

  • 登錄AppGallery Connect並創建應用。
  • 創建Android Studio工程。
  • 生成簽名證書。
  • 生成簽名證書指紋。
  • 配置簽名證書指紋。
  • 添加應用包名並保存配置文件。
  • 在項目級build.gradle文件中,添加AppGallery Connect插件以及Maven倉地址。
  • 在Android Studio中配置簽名證書。

具體操作,請按照HMS Core集成準備中的詳細說明來完成。

啟用相關服務

前往「項目設置」頁面,選擇「API管理」並開通下述服務。

說明:部分API默認是關閉的,您必須手動啟用。

  • 賬號服務
  • 定位服務
  • 位置服務
  • 地圖服務

cke_821010.png

至此,您已成功啟用應用所需的華為服務。

集成賬號服務

通過華為帳號開放服務,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
                    )
                }
            }

cke_836834.png

接入地圖服務

地圖服務給您提供一套地圖開發調用的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)
     )
 }

cke_852739.png

集成定位服務

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文件中添加位置權限。

<uses-permission 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)
         }
     }
 }

cke_870584.png

集成位置服務

位置服務提供位置查詢服務,幫助您的用戶更加方便地使用位置相關服務,包括關鍵字搜索、周邊搜索、地點詳情、及地點搜索建議等,有助於您的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)
 }

cke_889359.png

集成雲數據庫

在這個步驟中,您可以學到:

  • 如何使用雲數據庫開發應用
  • 如何向雲數據庫中寫入應用數據
  • 如何查詢數據
  • 如何實時監聽數據變化
  • 端側和雲側的數據同步
  • 華為雲數據庫是一款端雲協同的數據庫產品,提供端雲數據的協同管理、統一的數據模型和豐富的數據管理API接口等能力。

該雲數據庫十分契合本工程項目,有利於我們進行數據的增、刪、查、改操作。

開發準備

使用雲數據庫構建應用服務需完成下述準備工作:

  • 在AppGallery Connect註冊賬號並通過實名認證。
  • 登錄AppGallery Connect創建項目及應用。
  • 開通AppGallery Connect匿名帳號認證服務,使應用有認證用戶的相關權限。
  • 本地上安裝Android Studio。

開通服務

使用雲數據庫前,您需要先開通服務。

1.登錄AppGallery Connect,點擊「我的項目」。

2.在項目列表中選擇項目和需要開通雲數據庫的應用。

3.在導航樹上選擇「構建」,點擊「雲數據庫」。

4.點擊「立即開通」。 

cke_905689.png

5.如果您之前沒有選擇數據處理位置,開通雲數據庫後首先將會設置數據處理位置。 

cke_919451.png

6.雲數據庫初始化完成後,該服務成功開通。

新增和導出對象類型

下述示例將展示如果在AppGallery Connect上新增和導出用於Android應用開發的java格式對象類型文件。

1.登錄AppGallery Connect,選擇「我的項目」。

2.在項目列表頁面中選擇項目,單擊項目下需要創建對象類型的應用。

3.在導航樹上選擇「構建」,點擊「雲數據庫」。

4.點擊「新增」,進入創建對象類型頁面。

cke_933347.png

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.點擊「導出」。

cke_971517.png

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.點擊「新增」,進入創建存儲區頁面。 

cke_997362.png

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"));
 }

cke_423080.gif寫入數據

您可以使用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<T> mSnapshotListener = (cloudDBZoneSnapshot, e) -> {
     if (e != null) {
         Log.w(TAG, "onSnapshot" );
         return;
     }
     CloudDBZoneObjectList<T> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
     List<T> 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<T> query) {
     if (mCloudDBZone == null) {
         Log.w(TAG, "CloudDBZone is null, try re-open it");
         return;
     }
     Task<CloudDBZoneSnapshot<T>> queryTask = mCloudDBZone.executeQuery(query,
             CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
     queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<T>>() {
         @Override
         public void onSuccess(CloudDBZoneSnapshot<T> 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<T> 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)
 }

cke_1018241.png

提示:

  • 請務必使用最新版本的依賴。
  • 請務必在對象類型中為用戶分配合理的角色。

說明:更多詳細信息,請參考雲數據庫接入文檔

恭喜您

祝賀您,您已成功構建UrbanHome。

參考

有關更多信息,請參閱以下官方文檔。

本Codelab中的示例代碼下載地址如下:源碼下載

欲了解更多更全技術文章,歡迎訪問//developer.huawei.com/consumer/cn/forum/?ha_source=zzh