전문가: Kotlin을 사용하는 Android의 택배 앱 MVVM Jetpack(HMS 위치 및 지도 키트) - Part-5
이 기사에서는 HMS Account, Push, CloudDB, AuthService, Push Kit Uplink Message, Location 및 Map Kit와 같은 HMS Core 키트를 통합할 Kotlin을 사용하여 Courier Android 애플리케이션을 만들 것입니다.
HMS Account 및 AuthService 키트를 부분적으로 통합했으며, HMS 푸시 키트를 사용한 푸시 알림(2부), 클라우드 DB 키트(3부) 및 화웨이 클라이언트 푸시 통합을 이 시리즈의 4부로 통합했습니다. 아래 링크로 들어가주세요-
파트 1 https://forums.developer.huawei.com/forumPortal/en/topic/0202841957497640128
파트 2 https://forums.developer.huawei.com/forumPortal/en/topic/0201847982965230092
3부 https://forums.developer.huawei.com/forumPortal/en/topic/0201854022878900124?fid=0101187876626530001
앱은 DataBinding, AndroidViewModel, Observer, LiveData 등과 같은 Jetpack 구성 요소를 사용하여 Android MVVM 클린 아키텍처를 사용합니다.
이 기사에서는 Observable 패턴을 사용하여 DataBinding을 구현하려고 합니다.
지도 키트 소개
Map Kit는 200개 이상의 국가 및 지역의 지도 데이터를 포함하고 70개 이상의 언어를 지원합니다. 사용자는 SDK를 사용하여 지도 기반 기능을 앱에 쉽게 통합할 수 있습니다. 지도 세부 정보 표시 기능을 최적화하고 강화합니다. Map Kit는 줌, 회전, 이동 및 기울기 제스처를 포함한 제스처를 지원하여 원활한 상호작용 경험을 보장합니다.
로케이션 키트 소개
Location Kit는 GPS, Wi-Fi 및 기지국 위치 기능을 앱에 결합하여 글로벌 포지셔닝 기능을 구축하고 전 세계 사용자를 대상으로 유연한 위치 기반 서비스를 제공할 수 있습니다. 현재 통합 위치, 활동 식별 및 지오펜스의 세 가지 주요 기능을 제공합니다. 필요에 따라 이러한 기능 중 하나 이상을 호출할 수 있습니다.
전제 조건
Huawei Phone EMUI 3.0 이상.
Huawei가 아닌 휴대폰 Android 4.4 이상(API 레벨 19 이상).
HMS 코어 APK 4.0.0.300 이상
안드로이드 스튜디오
AppGallery 계정
앱 갤러리 통합 프로세스
로그인하고 AppGallery Connect 포털에서 프로젝트를 생성하거나 선택합니다.
프로젝트 설정으로 이동하여 구성 파일을 다운로드합니다.
일반 정보로 이동한 다음 데이터 저장소 위치를 제공합니다.
앱 개발
필수 종속성 추가:
Android 스튜디오를 시작하고 새 프로젝트를 만듭니다. 일단 프로젝트가 준비되었습니다.
Gradle 스크립트 폴더로 이동하여 build.gradle(프로젝트: 앱)을 엽니다.
ext.kotlin_version = "1.4.21"
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
HMS Loaction 및 맵 키트에 대한 다음 종속성 추가
// Huawei Map
implementation 'com.huawei.hms:maps:6.2.0.301'
// Huawei Location Kit
implementation 'com.huawei.hms:location:6.2.0.300'
Gradle 스크립트 폴더로 이동하여 build.gradle(모듈: 앱)을 엽니다.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.huawei.agconnect'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
buildToolsVersion "29.0.3"
buildFeatures {
dataBinding = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
applicationId "com.hms.corrierapp"
minSdkVersion 27
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
implementation(
[group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.4.1'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.4.1'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.4.1'],
)
//HMS Kits
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302'
implementation 'com.huawei.hms:push:4.0.3.301'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.5.0.300'
implementation "com.huawei.agconnect:agconnect-auth-huawei:1.6.0.300"
implementation 'com.huawei.agconnect:agconnect-auth:1.5.0.300'
// Huawei Map
implementation 'com.huawei.hms:maps:6.2.0.301'
// Huawei Location Kit
implementation 'com.huawei.hms:location:6.2.0.300'
}
코드 구현
다음 패키지 모델인 push, viewmodel을 생성했습니다.
모델: 기본 폴더에서 새 패키지를 만들고 이름을 모델로 지정합니다.
MapActivity.kt:
package com.hms.corrierapp.map
import android.Manifest
import android.content.IntentSender
import android.content.pm.PackageManager
import android.location.Location
import android.os.*
import android.util.Log
import android.widget.Chronometer
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.hms.corrierapp.R
import com.huawei.hmf.tasks.Task
import com.huawei.hms.common.ApiException
import com.huawei.hms.common.ResolvableApiException
import com.huawei.hms.location.*
import com.huawei.hms.maps.*
import com.huawei.hms.maps.model.*
import java.io.File
import java.text.DecimalFormat
class MapActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMapView: MapView
private var mHwMap: HuaweiMap? = null
private var mPolylineOptions: PolylineOptions? = null
private var mListener: LocationSource.OnLocationChangedListener? = null
private var mMarkerStart: Marker? = null
private var mMarkerEnd: Marker? = null
private var mLocationRequest: LocationRequest? = null
private var mTvStart: TextView? = null
private var mTvDistance: TextView? = null
private var mTime: Chronometer? = null
private var fusedLocationProviderClient: FusedLocationProviderClient? = null
private val mPath: PathBean = PathBean()
private var mSeconds: Long = 0
private val mHandler = Handler(Looper.getMainLooper())
private val mDecimalFormat = DecimalFormat("0.00")
private var mIsRunning = false
private val mTimeRunnable: Runnable = object : Runnable {
override fun run() {
mTime!!.text = formatSeconds()
mHandler.postDelayed(this, 1000)
}
}
private var mLocationCallback: LocationCallback? = null
// GPS Data
private var mGpsDataThread: HandlerThread? = null
private var mGpsDataHandler: Handler? = null
private var mGpsDataFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
checkPermission()
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
// check location settings
checkLocationSettings()
// init MapView
mMapView = findViewById(R.id.hw_mapview)
var mapViewBundle: Bundle? = null
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
}
mMapView.onCreate(mapViewBundle)
mMapView.getMapAsync(this)
mTvDistance = findViewById(R.id.tv_distance)
mTime = findViewById(R.id.cm_time)
mTvStart = findViewById(R.id.tv_start)
mTvStart!!.setOnClickListener({ processStartClick() })
// Initializing Map Kit Polyline
mPolylineOptions = PolylineOptions()
mPolylineOptions!!.color(resources.getColor(R.color.colorAccent))
mPolylineOptions!!.width(5f)
// Recording GPS Data
mGpsDataFile = File(getExternalFilesDir(null), "GpsData.txt")
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk <= 28 Q")
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 1)
}
} else {
// Dynamically apply for permissions required for SDK > 28. Add the android.permission.ACCESS_BACKGROUND_LOCATION permission.
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 2)
}
}
}
override fun onMapReady(huaweiMap: HuaweiMap?) {
Log.d(TAG, "onMapReady: ")
mHwMap = huaweiMap
mHwMap?.isMyLocationEnabled = true
mHwMap?.uiSettings?.isZoomControlsEnabled = false
// Add Location Source
mHwMap?.setLocationSource(object : LocationSource {
override fun activate(onLocationChangedListener: LocationSource.OnLocationChangedListener?) {
mListener = onLocationChangedListener
}
override fun deactivate() {}
})
// Obtains the current position and updates the map camera.
try {
val lastLocation: Task<Location> = fusedLocationProviderClient!!.lastLocation
lastLocation.addOnSuccessListener { location ->
if (location ==null) return@addOnSuccessListener
mListener!!.onLocationChanged(location)
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}.addOnFailureListener {
Log.d(TAG, "onMapReady: Obtains the current position failure")
}
} catch (e: Exception) {
}
}
companion object {
private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
private const val TAG = "MapActivity"
}
override fun onStart() {
super.onStart()
mMapView.onStart()
}
override fun onStop() {
super.onStop()
mMapView.onStop()
}
override fun onDestroy() {
removeLocationUpdatesWithCallback()
super.onDestroy()
mHandler.removeCallbacksAndMessages(null)
mMapView.onDestroy()
}
override fun onPause() {
mMapView.onPause()
super.onPause()
mGpsDataThread!!.quitSafely()
try {
mGpsDataThread!!.join()
mGpsDataThread = null
mGpsDataHandler = null
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
override fun onResume() {
super.onResume()
mMapView.onResume()
mGpsDataThread = HandlerThread("DotThread")
mGpsDataThread!!.start()
mGpsDataHandler = Handler(mGpsDataThread!!.looper)
}
override fun onLowMemory() {
super.onLowMemory()
mMapView.onLowMemory()
}
private fun checkLocationSettings() {
val builder: LocationSettingsRequest.Builder = LocationSettingsRequest.Builder()
val locationSettingsRequest: LocationSettingsRequest = builder.build()
val settingsClient: SettingsClient = LocationServices.getSettingsClient(this)
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener { requestLocationUpdate() }.addOnFailureListener { e ->
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
val rae: ResolvableApiException = e as ResolvableApiException
rae.startResolutionForResult(this@MapActivity, 0)
} catch (sie: IntentSender.SendIntentException) {
}
}
}
}
private fun requestLocationUpdate() {
mLocationRequest = LocationRequest()
mLocationRequest!!.interval = 5000
mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
writeGpsData2Sdcard(locationResult.lastLocation)
if (mIsRunning) {
processLocationChange(locationResult.lastLocation)
}
}
override fun onLocationAvailability(locationAvailability: LocationAvailability?) {
super.onLocationAvailability(locationAvailability)
}
}
fusedLocationProviderClient
?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
?.addOnSuccessListener { Log.i(TAG, "request location updates success") }
?.addOnFailureListener { e ->
Log.e(TAG, "request location updates failed, error: " + e.message)
}
}
// Removed when the location update is no longer required.
private fun removeLocationUpdatesWithCallback() {
try {
val voidTask: Task<Void> =
fusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback)
voidTask.addOnSuccessListener { }.addOnFailureListener { }
} catch (e: Exception) {
Log.e(TAG, "removeLocationUpdatesWithCallback Exception : $e")
}
}
private fun processStartClick() {
if (mIsRunning) {
mIsRunning = false
mPath.endTime = (System.currentTimeMillis())
mTvStart!!.text = "Start"
mHandler.removeCallbacks(mTimeRunnable)
if (mPath.mPathLinePoints!!.size > 0) {
mPath.endPoint = (mPath.mPathLinePoints!!.get(mPath.mPathLinePoints!!.size - 1))
if (null != mMarkerStart && null != mMarkerEnd) {
mMarkerStart!!.remove()
mMarkerEnd!!.remove()
}
val startPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_location_marker))
.position(mPath.mStartPoint)
startPointOptions.title("Start Point")
startPointOptions.snippet("Start Point")
mMarkerStart = mHwMap!!.addMarker(startPointOptions)
val endPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_location_marker))
.position(mPath.mEndPoint)
endPointOptions.title("End Point")
endPointOptions.snippet("End Point")
mMarkerEnd = mHwMap!!.addMarker(endPointOptions)
}
} else {
mIsRunning = true
mPath.reset()
mPath.startTime = System.currentTimeMillis()
mHandler.post(mTimeRunnable)
mTvStart!!.text = "Stop"
}
}
private fun processLocationChange(location: android.location.Location) {
val latLng = LatLng(location.latitude, location.longitude)
if (mPath.mStartPoint == null) {
mPath.mStartPoint = latLng
}
mPath.addPoint(latLng)
val distance: Float = mPath.updateDistance()
val sportMile = distance / 1000.0
if (mSeconds > 0) {
val distribution = mSeconds.toDouble() / 60.0 / sportMile
mPath.setDistribution(distribution)
mTvDistance!!.text = mDecimalFormat.format(sportMile)
} else {
mPath.setDistribution(0.0)
mTvDistance!!.text = "0.00"
}
mPolylineOptions!!.add(latLng)
mHwMap!!.addPolyline(mPolylineOptions)
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}
fun formatSeconds(): String {
val hh = if (mSeconds / 3600 > 9) mSeconds / 3600 else mSeconds / 3600
val mm =
if (mSeconds % 3600 / 60 > 9) mSeconds % 3600 / 60 else mSeconds % 3600 / 60
mSeconds++
return "$hh:$mm"
}
private fun writeGpsData2Sdcard(location: Location) {
Log.d(
TAG,
"write latitude and longitude, latitude: " + location.latitude + ", longitude: " + location.longitude
)
mGpsDataHandler!!.post(
GpsDataSaver(
mGpsDataFile, """
${location.latitude}, ${location.longitude}
""".trimIndent()
)
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
}
}
}
activity_map.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/sport_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.huawei.hms.maps.MapView
android:id="@+id/hw_mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"
map:uiZoomControls="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="15dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="15dp"
android:background="@color/white"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_horizontal"
android:maxLength="8"
android:text="0.00"
android:textColor="#000000"
android:textSize="25sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginBottom="2.5dp"
android:gravity="center_horizontal"
android:text="km"
android:textColor="#88000000"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_weight="2"
android:orientation="vertical">
<Chronometer
android:id="@+id/cm_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:ellipsize="end"
android:format="00:00"
android:gravity="center"
android:textColor="#000000"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginBottom="2.5dp"
android:gravity="center_horizontal"
android:text="Total time"
android:textColor="#88000000"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_start"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:gravity="center"
android:text="Track Courier Location"
android:textColor="@color/colorPrimary"
android:textSize="21sp"
android:textStyle="bold" />
</RelativeLayout>
activity_delivery_status.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/delivery"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="34sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" />
<View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Booking"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Done"
android:textAlignment="center"
android:textColor="@color/green"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Payment Pending"
android:textAlignment="center"
android:textColor="@color/red"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" />
<View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PickUp"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Done"
android:textAlignment="center"
android:textColor="@color/green"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Shipping"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" />
<View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delivery"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="On It's Way"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Tracking ID: 12231223"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_done"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="5dp"
android:background="@color/colorPrimaryDark"
android:text="See On Map"
android:textColor="@color/white"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</RelativeLayout>
</layout>
앱 빌드 결과
팁과 요령
minSDK 버전을 24 이상으로 설정하십시오. 그렇지 않으면 AndriodManifest 병합 문제가 발생합니다.
앱 폴더에 agconnect-services.json 파일을 추가했는지 확인합니다.
반드시 SHA-256 지문을 추가했는지 확인하십시오.
모든 종속성이 제대로 추가되었는지 확인하십시오.
결론
이 기사에서는 Android 애플리케이션에서 HMS 계정, 푸시, CloudDB, AuthService, 푸시 키트 업링크 메시지, 위치 및 맵 키트를 통합하는 방법을 배웠습니다. 이 기사를 완전히 읽은 후 사용자는 Kotlin을 사용하여 Courier Android 애플리케이션에서 Location 및 Map Kit를 쉽게 구현할 수 있습니다.
이 기사를 읽어 주셔서 감사합니다. 이 글이 도움이 되셨다면 좋아요와 댓글을 꼭 남겨주세요. 그건 나에게 큰 의미 야.
참조
HMS 문서:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001050048870
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-brief-introduction-0000001061991343
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-introduction-0000001121930588
위치 및 지도 키트 교육 비디오:
https://developer.huawei.com/consumer/en/training/course/video/201575277450653242
Reference
이 문제에 관하여(전문가: Kotlin을 사용하는 Android의 택배 앱 MVVM Jetpack(HMS 위치 및 지도 키트) - Part-5), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/hmscommunity/expert-courier-app-mvvm-jetpack-hms-location-and-map-kit-in-android-using-kotlin-part-5-3g0n텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)