mapbox-maps-android icon indicating copy to clipboard operation
mapbox-maps-android copied to clipboard

should not be stored and used after MapView is destroyed, when touching mapView.location after `onStop` lifecycle event

Open mfazekas opened this issue 2 years ago • 5 comments

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

mfazekas avatar Feb 18 '23 12:02 mfazekas