mapbox-maps-android
                                
                                 mapbox-maps-android copied to clipboard
                                
                                    mapbox-maps-android copied to clipboard
                            
                            
                            
                        should not be stored and used after MapView is destroyed, when touching mapView.location after `onStop` lifecycle event
Environment
- Android OS version: 11.0
- Devices affected: Android emulator
- Maps SDK Version: 10.11.0 - 30539c059ec92e2276c74f6def4c068bed7941d7
Observed behavior and steps to reproduce
Calling
mapView.location.updateSettings {
  pulsingEnabled = false
}
on a stopped map will reactivate location component and after subsequent onDestroy it causes error like this:
2023-02-18 13:26:59.257 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing styleLayerExists) should not be stored and used after MapView is destroyed or new style has been loaded.
2023-02-18 13:26:59.259 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing setStyleLayerProperty) should not be stored and used after MapView is destroyed or new style has been loaded.
2023-02-18 13:26:59.266 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\MapboxLocationProvider]: Missing location permission, location component will not take effect before location permission is granted.
2023-02-18 13:26:59.270 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing styleLayerExists) should not be stored and used after MapView is destroyed or new style has been loaded.
2023-02-18 13:26:59.273 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing setStyleLayerProperty) should not be stored and used after MapView is destroyed or new style has been loaded.
2023-02-18 13:26:59.315 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing styleLayerExists) should not be stored and used after MapView is destroyed or new style has been loaded.
2023-02-18 13:26:59.321 10130-10130 Mapbox                  com.mapbox.maps.testapp              W  [maps-android\Mbgl-Style]: Style object (accessing setStyleLayerProperty) should not be stored and used after MapView is destroyed or new style has been loaded.
To reproduce change FragmentBackStackActivity.kt to the component below.
Then run the sample app, open Map Fragment the go back to the menu.
Expected behavior
mapView.location.updateSettings should emit a warn about not being usable in stopped state. But more ideally it should not reactivate the locationComponent.
Notes / preliminary analysis
Of course our situation is more complex. We're embedding mapbox into react-native environment. We have a location component, and it seems that it's removed after onStop is called, but before onDestroy. When it's removed we change settings of location.
The mapbox code ends up in appySettings that does all activateLocationComponent as I have not changed the enabled setting, and neither is isLocationComponentActivated since onStop event happened.
https://github.com/mapbox/mapbox-maps-android/blob/30539c059ec92e2276c74f6def4c068bed7941d7/plugin-locationcomponent/src/main/java/com/mapbox/maps/plugin/locationcomponent/LocationComponentPluginImpl.kt#L370-L384
Additional links and references
Replace FragmentBackStackActivity.kt with the following code
package com.mapbox.maps.testapp.examples
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.mapbox.maps.plugin.locationcomponent.location
import com.mapbox.maps.MapView
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityEmptyFabBinding
import com.mapbox.maps.testapp.examples.fragment.MapFragment
class FragmentBackStackActivity : AppCompatActivity() {
  private lateinit var mapFragment: MapFragment
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ActivityEmptyFabBinding.inflate(layoutInflater)
    setContentView(binding.root)
    if (savedInstanceState == null) {
      mapFragment = MapFragment()
      mapFragment.getMapAsync {
        initMap(it, mapFragment.getMapView())
      }
      supportFragmentManager.beginTransaction().apply {
        add(R.id.container, mapFragment, FRAGMENT_TAG)
      }.commit()
      mapFragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
        override fun onStop(owner: LifecycleOwner) {
          Log.e("LC", "onStop")
          val mapView = mapFragment.getMapView()
          mapView.location.updateSettings {
            pulsingEnabled = false
          }
        }
      })
    } else {
      supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)?.also { fragment ->
        if (fragment is MapFragment) {
          fragment.getMapAsync {
            initMap(it, fragment.getMapView())
          }
        }
      }
    }
    binding.displayOnSecondDisplayButton.setOnClickListener { handleClick() }
    binding.fragmentButton.setOnClickListener { addNewFragment() }
  }
  private fun addNewFragment() {
    supportFragmentManager.beginTransaction().apply {
      replace(
        R.id.container,
        MapFragment().also {
          it.getMapAsync { mapboxMap -> initMap(mapboxMap, it.getMapView()) }
        }
      )
      addToBackStack("map_new_fragment_${System.currentTimeMillis()}")
    }.commit()
  }
  private fun initMap(mapboxMap: MapboxMap, mapView: MapView) {
    mapboxMap.loadStyleUri(Style.SATELLITE)
    mapView.location.updateSettings {
      enabled = true
      pulsingEnabled = true
    }
  }
  private fun handleClick() {
    supportFragmentManager.beginTransaction().apply {
      replace(R.id.container, EmptyFragment.newInstance())
      addToBackStack("map_empty_fragment")
    }.commit()
  }
  class EmptyFragment : androidx.fragment.app.Fragment() {
    companion object {
      fun newInstance(): EmptyFragment {
        return EmptyFragment()
      }
    }
    @SuppressLint("SetTextI18n")
    override fun onCreateView(
      inflater: LayoutInflater,
      container: ViewGroup?,
      savedInstanceState: Bundle?
    ): View {
      val textView = TextView(inflater.context)
      textView.text = "This is an empty Fragment"
      return textView
    }
  }
  companion object {
    private const val FRAGMENT_TAG = "map_fragment"
  }
}
See #1715