firstly, the code:
class MapFragment: BaseFragment<MapFragmentBinding>(R.layout.map_fragment) {
var mapView: MapView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView = binding.mapView
MapsInitializer.initialize(requireContext(),
MapsInitializer.Renderer.LATEST
) {}
// seems a bit out of place, but due to the binding variable, from out baseFragment class, it has to be done here
mapView?.onCreate(savedInstanceState)
mapView?.getMapAsync { gMap ->
gMap.setOnMapLoadedCallback {
val randomLocation = LatLng(
Random.nextDouble(-170.0, 170.0),
Random.nextDouble(-170.0, 170.0)
)
gMap.addMarker(MarkerOptions().position(randomLocation).title(randomLocation.toString()))
gMap.animateCamera(CameraUpdateFactory.newLatLngZoom(randomLocation, 10.0f))
}
}
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onPause() {
super.onPause()
mapView?.onPause()
}
override fun onResume() {
super.onResume()
mapView?.onResume()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView?.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView?.onSaveInstanceState(outState)
}
}
this is a mapFragment that is included into another fragment. It is reloaded fairly often (to be precise, the app has a recycler view filled with items, whenever one is pressed this fragment is visible).
I am not sure why but sometimes (whenever it is opened and closed 3-7 times) it animates the camera to random location, and does not display the marker at all. I noticed that it happens mostly whenever the "random location" is at Arctic Ocean or Antarctica (believe me I don't know whether that is related at all, I am as confused as you are).
Any hints/ideas why that might be happening?
Your range of a random latitude is invalid -170.0, 170.0. The map camera implementation will likely peg the out-of-range value to -90 or 90 and the map will refuse to place the marker.
A maximum range for random position could use :
val randomLocation = LatLng(
Random.nextDouble(-90.0, 90.0), // kotlin Random produces [-90,90)
Random.nextDouble(-180.0, 180.0)
The maps api for latitude permits:
[-90, 90] // inclusive at both ends
and for longitude permits:
[-180, 180) // inclusive on the negative and exclusive on the positive
Related
I am implementing a call answer screen. In this screen, I am using both proximity sensor and power manager wake lock to create the effect of screen turning off when the phone is close to your face. I've managed to implement the feature, however it is causing memory leak. Since I detected the leak when the fragment is still simple and contains few code, I've removed several classes and functions to trace and confirm the cause of the leak. I've managed to narrow down the cause of the leak to PowerManager.WakeLock.
this is the code that I use to implement the feature in the fragment. I've tried to release the wake lock on multiple point in fragment lifecycle, however it still causes memory leak.
override val sensorManager: SensorManager
get() = requireContext().getSystemService(Context.SENSOR_SERVICE) as SensorManager
override var proximitySensor: Sensor? = null
override val powerManager: PowerManager =
requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
override val lock: PowerManager.WakeLock = powerManager.newWakeLock(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
PROXIMITY_WAKE_LOG_TAG
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
activity?.onBackPressedDispatcher?.addCallback {
activity?.finishAndRemoveTask()
}
}
override fun onResume() {
super.onResume()
proximitySensor?.let { proximity ->
sensorManager.apply {
registerListener(
this#AnswerFragment,
proximity,
SensorManager.SENSOR_DELAY_NORMAL
)
}
}
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
if (lock.isHeld) {
lock.release()
}
}
override fun onDestroyView() {
super.onDestroyView()
proximitySensor = null
if (lock.isHeld) {
lock.release()
}
_binding = null
}
override fun onDestroy() {
if (lock.isHeld) {
lock.release()
}
super.onDestroy()
}
override fun onSensorChanged(event: SensorEvent?) {
if (event?.values?.get(0) == 0.0f) {
// Object is near phone, turn off screen
lock.acquire()
} else {
// Object is not near phone, turn on screen
if (lock.isHeld) {
lock.release()
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
As a side note: I am also getting this error in my log
WakeLock finalized while still held
This is a known issue. See the LeakCanary issue and the underlying tools issue. For now, I recommend that you consider it to be a false positive, and ignore the leak.
I have a Fragment with Mapbox and I want to display the device location on it.
class SampleMapFragment : Fragment(), PermissionsListener {
private lateinit var binding: FragmentExploreBinding
#Inject
lateinit var permissionsManager: PermissionsManager
private lateinit var mapboxMap: MapboxMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Mapbox.getInstance(requireContext().applicationContext, getString(R.string.mapbox_token))
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (!::binding.isInitialized) {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_explore,
container,
false
)
binding.lifecycleOwner = this
binding.mapView.onCreate(savedInstanceState)
setUpMap()
}
return binding.root
}
private fun setUpMap() {
binding.mapView.getMapAsync { mapboxMap ->
this.mapboxMap = mapboxMap
mapboxMap.setStyle(Style.MAPBOX_STREETS) { loadedMapStyle ->
starLocationTracking(loadedMapStyle)
}
}
}
private fun starLocationTracking(loadedMapStyle: Style) {
if (!PermissionsManager.areLocationPermissionsGranted(requireContext())) {
permissionsManager.requestLocationPermissions(requireActivity())
return
}
initLocationComponent(loadedMapStyle)
}
private fun initLocationComponent(loadedMapStyle: Style) {
val customLocationComponentOptions = LocationComponentOptions.builder(requireActivity())
.elevation(5f)
.accuracyAlpha(.6f)
.accuracyColor(Color.RED)
.build()
val locationComponentActivationOptions = LocationComponentActivationOptions.builder(
requireActivity(),
loadedMapStyle
)
.locationComponentOptions(customLocationComponentOptions)
.build()
mapboxMap.locationComponent.apply {
activateLocationComponent(locationComponentActivationOptions)
isLocationComponentEnabled = true
renderMode = RenderMode.COMPASS
cameraMode = CameraMode.TRACKING
}
}
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
// TODO some explanation can be shown here
}
override fun onPermissionResult(granted: Boolean) {
if (granted) mapboxMap.getStyle { loadedStyle -> starLocationTracking(loadedStyle) }
//else TODO some explanation can be shown here
}
fun onMapBoxRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) = permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
override fun onStart() {
super.onStart()
binding.mapView.onStart()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
}
override fun onPause() {
super.onPause()
binding.mapView.onPause()
}
override fun onStop() {
super.onStop()
binding.mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
releaseMapResources()
releasePermissionsManagerListener()
}
private fun releaseMapResources() {
binding.mapView.onDestroy()
}
private fun releasePermissionsManagerListener() {
permissionsManager.listener = null
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.mapView.onSaveInstanceState(outState)
}
override fun onLowMemory() {
super.onLowMemory()
binding.mapView.onLowMemory()
}
}
I tested the implementation above with 2 real devices and 1 emulator.
The solution works fine with the Android 10 device. User location is found, even after closing the location services and opening it again after the map, is visible.
But location is sometimes not found, sometimes it take very long, even the location services are ready before the map is visible while trying with Android 9 and Android 7 devices.
What could be the problem here? Any help would be appreciated.
"But location is sometimes not found, sometimes it take very long,
even the location services are ready before the map is visible while
trying with Android 9 and Android 7 devices."
This sounds as if the device tries to obtain the location from the GPS sensor, but takes long to retrieve it. With newer devices and Android 10 localization has been improved, so this could explain what you are seeing.
To understand what is going on, I would recommend to implement the Android FusedLocationProvider and compare the behaviour to the one wrapped by Mapbox SDKs (locationComponent).
Also, please check whether the "snappy" location you get with Android 10 is actually retrieved from the GPS sensor, or if it is "just" the last known location, that was cached by Android.
So I recently migrated to navigation components (2.2.0-alpha01). As I was working on a high end device I didn't really noticed any slowdowns, but as soon as I finished, testers started reporting slugish app navigation.
In my navigation code I use calls like findNavController().navigate(CustomFragmentDirections.actionEtc()) or findNavController().popBackStack(fragmentId, false)
I also use safeargs with navigation.
In my navigation xml I have actions that heavily rely on popUpTo and app:launchSingleTop="true"
To investigate I made very basic profiler in my BaseFragment class:
private var lastTimestamp = System.currentTimeMillis()
protected fun getEllapsedTime(): String {
val currTime = System.currentTimeMillis()
val elapsedTime = currTime - lastTimestamp
lastTimestamp = currTime
return "${elapsedTime}ms"
}
override fun onAttach(context: Context) {
Timber.d("onAttach(${javaClass.simpleName})(${getEllapsedTime()})")
super.onAttach(context)
}
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("onCreate(${javaClass.simpleName})(${getEllapsedTime()})")
super.onCreate(savedInstanceState)
savedInstanceState?.let { restoreState(it) }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Timber.d("onCreateView(${javaClass.simpleName})(${getEllapsedTime()})")
return inflater.inflate(layoutRes, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Timber.d("onViewCreated(${javaClass.simpleName})(${getEllapsedTime()})")
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
Timber.d("onActivityCreated(${javaClass.simpleName})(${getEllapsedTime()})")
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
Timber.d("onStart(${javaClass.simpleName})(${getEllapsedTime()})")
super.onStart()
}
override fun onResume() {
Timber.d("onResume(${javaClass.simpleName})(${getEllapsedTime()})")
super.onResume()
requireActivity().window?.decorView?.post {
firstFrameRendered()
}
}
private fun firstFrameRendered() {
Timber.d("onFrameRendered(${javaClass.simpleName})(${getEllapsedTime()})")
}
override fun onPause() {
Timber.d("onPause(${javaClass.simpleName})(${getEllapsedTime()})")
super.onPause()
}
override fun onStop() {
Timber.d("onStop(${javaClass.simpleName})(${getEllapsedTime()})")
super.onStop()
}
override fun onDestroyView() {
Timber.d("onDestroyView(${javaClass.simpleName})(${getEllapsedTime()})")
super.onDestroyView()
}
override fun onDestroy() {
Timber.d("onDestroy(${javaClass.simpleName})(${getEllapsedTime()})")
super.onDestroy()
}
override fun onDetach() {
Timber.d("onDetach(${javaClass.simpleName})(${getEllapsedTime()})")
super.onDetach()
}
I tried profiling using android studio profiler, but didn't really noticed anything out of the ordinary. I also tried window.addOnFrameMetricsAvailableListener but it pretty much gave me same results as my profiler code. The main method of importance is onFrameRendered. Basic Idea is to let layout inflate and render and immediately after screen is rendered count how many milliseconds passed since onResume was called.
I tried different devices and timings were not very consistent, but after measuring same transitions many times I noticed some tendency, that all my layouts now take almost twice as long to load when compared to previous app navigation which was using simple supportFragmentManager transactions.
I tried isolating navigation from one fragment to the other and I would always get this poor performance.
At the moment I know it has something to do with the way navigation switches fragments, because if I mock NavController with my custom code that just directly uses FragmentManager I get the same good performance as the old code. Will update the question if I'll find the exact problem.
Meanwhile, does anyone have any ideas what might be wrong?
I'm working with a map activity in android studio.
I need to wait that the locationListener get the first position of the user to start a function. How can I do?
You can wait for onMapReady callback , and you can know that your map not null :
#Override
public void onMapReady(final GoogleMap googleMap) {
//your code
}
You can set a callback for the OnMapLoaded event:
...
private var map: GoogleMap? = null
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mapViewBundle = savedInstanceState?.getBundle(MAP_VIEW_BUNDLE_KEY)
mapView.onCreate(mapViewBundle)
...
mapView.getMapAsync {
map = it
}
map?.setOnMapLoadedCallback {
Toast.makeText(requireContext(), "Loaded...", Toast.LENGTH_SHORT).show()
map?.snapshot(callback) // OK to take snapshot after load
}
}
I'm trying to load this webmap (https://oebb.maps.arcgis.com/home/item.html?id=f89eab37e55540f7b2e25a88cd0a07d5), but most of the layers don't show up.
It works on the web, but fails on Android (with the newest sdk, 100.2.1).
class MainActivity : AppCompatActivity() {
lateinit var mMapView: MapView
private lateinit var map: ArcGISMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// inflate MapView from layout
mMapView = findViewById(R.id.mapview)
val leftPoint = Point(16.374120968073157, 48.186396114084104, SpatialReferences.getWgs84())
val rightPoint = Point(16.38101960965946, 48.18357774813336, SpatialReferences.getWgs84())
val initialExtent = Envelope(leftPoint, rightPoint)
//construct a map from the portal item
map = ArcGISMap("https://oebb.maps.arcgis.com/home/item.html?id=f89eab37e55540f7b2e25a88cd0a07d5")
// set starting envelope for the ArcGISMap
map.initialViewpoint = Viewpoint(initialExtent)
// Pass a WebMap to the MapView constructor overload to display it.
mapview.map = map
}
override fun onPause() {
super.onPause()
mMapView.pause()
}
override fun onResume() {
super.onResume()
mMapView.resume()
}
override fun onDestroy() {
super.onDestroy()
mMapView.dispose()
}
}
9 of the 10 layers have an exception (map.operationalLayer[0].loadError: ArcGISRuntimeException: Invalid JSON.
Your code appears to be fine, the issue is probably inside the 100.2.1 Android SDK loading the web map. This issue should be fixed in version 100.3.0 which is due out in a few weeks. I ran your code on an internal build and your layers and symbols rendered similar to the web version.