首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >FusedLocationProviderClient和LocationCallback()对象查询

FusedLocationProviderClient和LocationCallback()对象查询
EN

Stack Overflow用户
提问于 2022-06-11 18:25:14
回答 1查看 315关注 0票数 0

总之,我实现了google来创建一个运行跟踪器,同时也实现了MVVM架构。但是,当我设法将我的代码配置为将LocationCallback对象放置在我的viewModel和fusedLocationProviderClient中时,我的LocationCallback对象在我试图实现的片段访问之后根本无法被调用。我试图在对象中记录任何东西,但什么也没有出现。想知道我是不是把它搞错了。我已经在我的主要活动中请求了许可。

RunSessionViewModel.kt

代码语言:javascript
复制
package com.example.myfit_exercisecompanion.ui.viewModels

import android.Manifest
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build

import android.os.Looper
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts

import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData

import com.example.myfit_exercisecompanion.repository.RunSessionRepository
import com.example.myfit_exercisecompanion.ui.MainActivity
import com.google.android.gms.location.*
import com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.SphericalUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import timber.log.Timber
import javax.inject.Inject
import kotlin.math.roundToInt

@HiltViewModel
class RunSessionViewModel @Inject constructor(
    private val runSessionRepository: RunSessionRepository,
    application: Application
): AndroidViewModel(application)  {

    private val _locationResults = MutableLiveData<LocationResult>()
            val locationResults: LiveData<LocationResult>
            get() = _locationResults

    val context = application

    val mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context.applicationContext)

    val ui = MutableLiveData(Ui.EMPTY)

    private val _locations = mutableListOf<LatLng>()
        val locations: List<LatLng>
        get() = _locations
    private var distance = 0

    private val _liveLocations = MutableLiveData<List<LatLng>>()
    val liveLocations: MutableLiveData<List<LatLng>>
        get() = _liveLocations

    private val _liveDistance = MutableLiveData<Int>()
    val liveDistance: MutableLiveData<Int>
        get() = _liveDistance

    private val _liveLocation = MutableLiveData<LatLng>()
    val liveLocation: MutableLiveData<LatLng>
        get() = _liveLocation


    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            super.onLocationResult(result)

            val currentLocation = result.lastLocation
            val latLng = LatLng(currentLocation!!.latitude, currentLocation.longitude)

            val lastLocation = _locations.lastOrNull()

            if (lastLocation != null) {
                distance += SphericalUtil.computeDistanceBetween(lastLocation, latLng).roundToInt()
                _liveDistance.value = distance
            }

            _locations.add(latLng)
            _liveLocations.value = locations
        }
    }

    @SuppressLint("MissingPermission")
    fun getUserLocation() {
        mFusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
            val latLng = LatLng(location.latitude, location.longitude)
            _locations.add(latLng)
            _liveLocation.value = latLng
        }
            .addOnFailureListener { failure ->
                Timber.d("lastLocation failure ${failure.message}")
            }
    }

    @SuppressLint("MissingPermission")
    private fun trackUser() {
        val locationRequest = LocationRequest.create().apply {
            priority = PRIORITY_HIGH_ACCURACY
            interval = 5000L
            fastestInterval = 2000L
        }
        mFusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
    }

    fun stopTracking() {
        mFusedLocationProviderClient.removeLocationUpdates(locationCallback)
        _locations.clear()
        distance = 0
        RPMLiveData().unloadStepCounter()
    }

    fun startTracking() {
        trackUser()

        val currentUi = ui.value
        ui.value = currentUi?.copy(
            formattedPace = Ui.EMPTY.formattedPace,
            formattedDistance = Ui.EMPTY.formattedDistance
        )
    }


    private val _liveSteps = MutableLiveData<Int>()
    val liveSteps:MutableLiveData<Int>
        get() = _liveSteps


    val rpmLiveData = RPMLiveData()

    // inner class just to have access to application
    inner class RPMLiveData : LiveData<String>(), SensorEventListener {
        private val sensorManager by lazy {
            context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
        }

        private val stepCounterSensor: Sensor? by lazy {
            sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
        }

        private var initialSteps = -1

        fun setupStepCounter() {
            if (stepCounterSensor != null) {
                sensorManager.registerListener(this, stepCounterSensor,
                    SensorManager.SENSOR_DELAY_FASTEST
                )
            }
        }

        override fun onSensorChanged(event: SensorEvent) {
            event.values.firstOrNull()?.toInt()?.let { newSteps ->
                if (initialSteps == -1) {
                    initialSteps = newSteps
                }

                val currentSteps = newSteps - initialSteps

                _liveSteps.value = currentSteps
            }
        }

        fun unloadStepCounter() {
            if (stepCounterSensor != null) {
                sensorManager.unregisterListener(this)
            }
        }

        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) = Unit
    }
}


data class Ui(
    val formattedPace: String,
    val formattedDistance: String,
    val currentLocation: LatLng?,
    val userPath: List<LatLng>
) {

    companion object {
        val EMPTY = Ui(
            formattedPace = "",
            formattedDistance = "",
            currentLocation = null,
            userPath = emptyList()
        )
    }
}

RunTrackerFragment.kt

代码语言:javascript
复制
package com.example.myfit_exercisecompanion.ui.fragments

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.MutableLiveData
import com.example.myfit_exercisecompanion.R
import com.example.myfit_exercisecompanion.databinding.FragmentRunTrackerBinding
import com.example.myfit_exercisecompanion.ui.MainActivity
import com.example.myfit_exercisecompanion.ui.viewModels.RunSessionViewModel
import com.example.myfit_exercisecompanion.ui.viewModels.Ui
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.PolylineOptions
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import javax.inject.Inject

@AndroidEntryPoint
class RunTrackerFragment : Fragment(R.layout.fragment_run_tracker), OnMapReadyCallback {

    private lateinit var map: GoogleMap
    private var _binding: FragmentRunTrackerBinding? = null
    // This property is only valid between onCreateView and
// onDestroyView.
    private val binding get() = _binding!!

    private val viewModel: RunSessionViewModel by viewModels()

    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

    var initialSteps = -1


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentRunTrackerBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        Timber.d("fusedLocationProviderClient not being a bitch ${viewModel.mFusedLocationProviderClient}")

        val mapFragment = childFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        binding.btnStartStop.setOnClickListener {
            if (binding.btnStartStop.text == getString(R.string.start_label)) {
                startTracking()
                binding.btnStartStop.setText(R.string.stop_label)
            } else {
                stopTracking()
                binding.btnStartStop.setText(R.string.start_label)
            }
        }
        viewModel.liveLocations.observe(viewLifecycleOwner) { locations ->
            val current = viewModel.ui.value
            viewModel.ui.value = current?.copy(userPath = locations)
        }

        viewModel.liveLocation.observe(viewLifecycleOwner) { currentLocation ->
            val current = viewModel.ui.value
            viewModel.ui.value = current?.copy(currentLocation = currentLocation)
        }

        viewModel.liveDistance.observe(viewLifecycleOwner) { distance ->
            val current = viewModel.ui.value
            val formattedDistance = requireContext().getString(R.string.distance_value, distance)
            viewModel.ui.value = current?.copy(formattedDistance = formattedDistance)
        }

        viewModel.liveSteps.observe(viewLifecycleOwner) { steps ->
            val current = viewModel.ui.value
            viewModel.ui.value = current?.copy(formattedPace = "$steps")
        }
    }

    private val locationPermissionProviderContract = activity?.registerForActivityResult(
        ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) {
            viewModel.getUserLocation()
            Timber.d("Pass 1")
        }
    }

    private val activityRecognitionPermissionProviderContract = activity?.registerForActivityResult(
        ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) {
            viewModel.RPMLiveData().setupStepCounter()
            Timber.d("Pass 2")
        }
    }

    fun requestUserLocation() {
        locationPermissionProviderContract?.launch(Manifest.permission.ACCESS_FINE_LOCATION)
    }

    fun requestActivityRecognition() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            activityRecognitionPermissionProviderContract?.launch(Manifest.permission.ACTIVITY_RECOGNITION)
        } else {
            viewModel.RPMLiveData().setupStepCounter()
        }
    }

    fun onMapLoaded(){
        requestUserLocation()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onMapReady(googleMap: GoogleMap) {
        map = googleMap
//        presenter = (activity as MainActivity).presenter

        viewModel.ui.observe(viewLifecycleOwner) { ui ->
            Log.d("ui instantiated", viewModel.ui.value.toString())
            updateUi(ui)
        }

        onMapLoaded()
        map.uiSettings.isZoomControlsEnabled = true
    }

    private fun startTracking() {
        binding.container.txtPace.text = ""
        binding.container.txtDistance.text = ""
        binding.container.txtTime.base = SystemClock.elapsedRealtime()
        binding.container.txtTime.start()
        map.clear()
        Log.d("type:", "${binding.container.txtTime.base}")

        viewModel.startTracking()
        requestActivityRecognition()
    }

    private fun stopTracking() {
        viewModel.stopTracking()
        binding.container.txtTime.stop()
    }

    @SuppressLint("MissingPermission")
    private fun updateUi(ui: Ui) {
        if (ui.currentLocation != null && ui.currentLocation != map.cameraPosition.target) {
            map.isMyLocationEnabled = true
            map.animateCamera(CameraUpdateFactory.newLatLngZoom(ui.currentLocation, 14f))
        }
        binding.container.txtDistance.text = ui.formattedDistance
        binding.container.txtPace.text = ui.formattedPace
        drawRoute(ui.userPath)
    }

    private fun drawRoute(locations: List<LatLng>) {
        val polylineOptions = PolylineOptions()

        map.clear()

        val points = polylineOptions.points
        points.addAll(locations)

        map.addPolyline(polylineOptions)
    }

}

由于我的代码的结构方式,映射出现在我的片段上,而不是在我当前的位置。问题

我试图遵循的指南实际上就是这里的链接,我想将其转换为MVVM架构。https://www.raywenderlich.com/28767779-how-to-make-an-android-run-tracking-app

这个平台上的第一个问题,也是android开发中相对较新的问题,请原谅我是否错误地发布了这个问题,或者我的代码是否非常不一致。

EN

回答 1

Stack Overflow用户

发布于 2022-06-11 20:31:51

我在我的应用程序中使用了类LocationLiveData,它扩展了MutableLiveData,并且工作非常完美。请参见下面的代码:

代码语言:javascript
复制
class LocationLiveData private constructor(context: Context) : MutableLiveData<Location>() {

    private val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)

    private var minAccuracy: Float = 0.toFloat()
    private var stopWhenCatch: Boolean = false
    private var stopped: Boolean = false

    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            locationResult?.lastLocation?.let { location ->
                if (location.accuracy <= minAccuracy) {
                    checkLocationAccuracy()

                    value = location
                    if (stopWhenCatch) {
                        stopped(true)
                        stopWatch()
                    }
                }
            }
        }
    }

    override fun onActive() {
        if (value == null || stopped) {
            startWatch()
        }
    }

    override fun onInactive() {
        stopWatch()
    }

    fun stopped(stopped: Boolean) {
        this.stopped = stopped
    }

    @SuppressLint("MissingPermission")
    private fun checkLocationAccuracy() {
        if (minAccuracy == DEFAULT_LOW_ACCURACY) {
            minAccuracy = DEFAULT_HIGH_ACCURACY
        }
    }

    @SuppressLint("MissingPermission")
    private fun startWatch() {
        client.requestLocationUpdates(createLocationHighAccuracyRequest(), locationCallback, Looper.myLooper())
    }

    private fun stopWatch() {
        client.removeLocationUpdates(locationCallback)
    }

    private fun createLocationHighAccuracyRequest() =
        LocationRequest
            .create()
            .setInterval(INTERVAL)
            .setFastestInterval(FASTEST_INTERVAL)
            .setSmallestDisplacement(DISTANCE)
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)

    class Builder(private val mContext: Context) {

        private var minAccuracy: Float = DEFAULT_LOW_ACCURACY
        private var stopWhenCatch: Boolean = false

        fun minAccuracy(minAccuracy: Float): Builder {
            this.minAccuracy = minAccuracy
            return this
        }

        fun stopWhenCatch(stopWhenCatch: Boolean): Builder {
            this.stopWhenCatch = stopWhenCatch
            return this
        }

        fun build() = LocationLiveData(mContext).apply {
            minAccuracy = this@Builder.minAccuracy
            stopWhenCatch = this@Builder.stopWhenCatch
        }
    }

    companion object {
        private const val DEFAULT_HIGH_ACCURACY = 50f
        private const val DEFAULT_LOW_ACCURACY = 10_000f // low accuracy for fast finding location on start application, 10km
        private const val INTERVAL = 10_000L
        private const val FASTEST_INTERVAL = 5_000L
        private const val DISTANCE = 5f
    }
}

来自ViewModel的代码:

代码语言:javascript
复制
private var locationLiveData: LocationLiveData? = null
fun subscribeLocationLiveData(context: Context): LocationLiveData {
    locationLiveData = LocationLiveData.Builder(context)
        .build()
    return locationLiveData!!
}
    
// call where you need to stop getting location
fun unsubscribeLocationLiveData() {
    locationLiveData?.stopped(true)
}

来自片段的代码:

代码语言:javascript
复制
private fun subscribeLocationUpdate() {
    viewModel.subscribeLocationLiveData(requireContext()).observe(
        viewLifecycleOwner,
        Observer { location ->
            // here will be user location
        }
    )
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72586909

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档