Skip to content

Snappy1

  • Home
  • Android
  • What
  • How
  • Is
  • Can
  • Does
  • Do
  • Why
  • Are
  • Who
  • Toggle search form

[FIXED] android – FusedLocationProviderClient and LocationCallback() object queries

Posted on November 11, 2022 By

Solution 1 :

I use class LocationLiveData in my application, which extends MutableLiveData and it works perfect. See code bellow:

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 = [email protected]
            stopWhenCatch = [email protected]
        }
    }

    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
    }
}

Code from ViewModel:

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

Code from fragment:

private fun subscribeLocationUpdate() {
    viewModel.subscribeLocationLiveData(requireContext()).observe(
        viewLifecycleOwner,
        Observer { location ->
            // here will be user location
        }
    )
}

Problem :

To sum up the issue, I was implementing the google maps API to make a run tracker and was also implementing MVVM architecture at the same time. However, when I managed to configure my code to place the LocationCallback object inside my viewModel and my fusedLocationProviderClient, my LocationCallback object is not able to be called at all ever after I visited the fragment I was trying to implement it. I tried to Log.d anything inside the object but nothing would show up. Was wondering if I implemented it wrong. I have already requested for permissions in my main activity.

READ  [FIXED] How to add SDK files manually that needs a package.xml in android studio?
Powered by Inline Related Posts

RunSessionViewModel.kt

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

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

}

because of the way my code is structured, the map appears on my fragment but not at my current location.
Problem

READ  [FIXED] Best way to activate/deactivate two layouts on top of each other in Android Studio?
Powered by Inline Related Posts

The guide I was trying to follow was actually this link here and I wanted to convert it into a MVVM architecture.
https://www.raywenderlich.com/28767779-how-to-make-an-android-run-tracking-app

The first question on this platform and relatively new to android development so pardon me if I had posted this wrongly or if my code is extremely inconsistent.

Comments

Comment posted by Isaac Chen

Do you put the LocationLiveData class as a parameter to your viewModel? I am using Dagger Hilt so when I did that there was an error that popped up saying I need to use

Comment posted by Roman Chekashov

@IsaacChen, no I just create by using builder

Android Tags:android, google-maps-android-api-2, kotlin

Post navigation

Previous Post: [FIXED] java – is permission needed when choosing image from gallery on android?
Next Post: [FIXED] Android Java: How to get component in a custom view? (Trying to access components via findByViewId and get null)

Related Posts

[FIXED] Android Studio: Banner ad over Webview Android
[FIXED] Android MVVM RecyclerView ClickListener Kotlin to Java Conversion Android
[FIXED] java – I have to develop the document status page where stages includes steps of home agreement Android
[FIXED] Setting input of Android TV using android App Android
[FIXED] Android BottomSheetBehavior setPeekHeight(int peekHeight, boolean animate) does not animate height transition Android
[FIXED] android – although method exists but I still receive the error :”Could not find method methodName(View) in a parent or ancestor Context” Android

Archives

  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022

Categories

  • ¿Cómo
  • ¿Cuál
  • ¿Cuándo
  • ¿Cuántas
  • ¿Cuánto
  • ¿Qué
  • Android
  • Are
  • At
  • C'est
  • Can
  • Comment
  • Did
  • Do
  • Does
  • Est-ce
  • Est-il
  • For
  • Has
  • Hat
  • How
  • In
  • Is
  • Ist
  • Kann
  • Où
  • Pourquoi
  • Quand
  • Quel
  • Quelle
  • Quelles
  • Quels
  • Qui
  • Should
  • Sind
  • Sollte
  • Uncategorized
  • Wann
  • Warum
  • Was
  • Welche
  • Welchen
  • Welcher
  • Welches
  • Were
  • What
  • What's
  • When
  • Where
  • Which
  • Who
  • Who's
  • Why
  • Wie
  • Will
  • Wird
  • Wo
  • Woher
  • you can create a selvedge edge: You can make the edges of garter stitch more smooth by slipping the first stitch of every row.2022-02-04
  • you really only need to know two patterns: garter stitch

Recent Posts

  • What are the main features of Islamic education?
  • Is the Jeep 4xe worth it?
  • How does the ringer work on cast iron?
  • What is the biggest size interior door?
  • Is blue raspberry an original Jolly Rancher flavor?

Recent Comments

No comments to show.

Copyright © 2023 Snappy1.

Powered by PressBook Grid Dark theme