Skip to content

Commit

Permalink
Handling Mapbox OpenGL crash on older devices and emulators.
Browse files Browse the repository at this point in the history
  • Loading branch information
HLCaptain committed Jul 31, 2024
1 parent 901c0d4 commit 42b349d
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 20 deletions.
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ dependencies {

// Mapbox
implementation(libs.mapbox.maps)
// implementation(libs.mapbox.maps.compose)
implementation(libs.mapbox.search)
implementation(libs.mapbox.navigation)

Expand Down
23 changes: 22 additions & 1 deletion app/src/main/java/illyan/jay/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
Expand All @@ -40,6 +47,7 @@ import illyan.jay.domain.interactor.AuthInteractor
import illyan.jay.ui.NavGraphs
import illyan.jay.ui.components.PreviewAccessibility
import illyan.jay.ui.theme.JayThemeWithViewModel
import illyan.jay.util.MapboxExceptionHandler
import javax.inject.Inject

@AndroidEntryPoint
Expand Down Expand Up @@ -78,14 +86,27 @@ class MainActivity : AppCompatActivity() {
}
}

val mapboxExceptionHandler = MapboxExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(mapboxExceptionHandler)

setContent {
var mapboxMapViewNotSupported by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
mapboxExceptionHandler.openGlNotSupportedCallback = {
mapboxMapViewNotSupported = true
}
}
JayThemeWithViewModel {
MainScreen(modifier = Modifier.fillMaxSize())
CompositionLocalProvider(LocalMapboxNotSupported provides mapboxMapViewNotSupported) {
MainScreen(modifier = Modifier.fillMaxSize())
}
}
}
}
}

val LocalMapboxNotSupported = compositionLocalOf { false }

@PreviewAccessibility
@Composable
fun MainScreen(
Expand Down
130 changes: 112 additions & 18 deletions app/src/main/java/illyan/jay/ui/map/Map.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,24 @@
package illyan.jay.ui.map

import android.content.Context
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowDownward
import androidx.compose.material.icons.rounded.BrokenImage
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -30,22 +45,27 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.mapbox.common.Cancelable
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.CameraState
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.ImageHolder
import com.mapbox.maps.MapInitOptions
import com.mapbox.maps.MapLoaded
import com.mapbox.maps.MapOptions
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.extension.observable.eventdata.MapLoadedEventData
import com.mapbox.maps.extension.style.expressions.dsl.generated.interpolate
import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.attribution.attribution
Expand All @@ -54,7 +74,9 @@ import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.maps.plugin.locationcomponent.LocationComponentPlugin
import com.mapbox.maps.plugin.logo.logo
import com.mapbox.maps.plugin.scalebar.scalebar
import illyan.jay.LocalMapboxNotSupported
import illyan.jay.R
import illyan.jay.ui.components.PreviewAll
import illyan.jay.ui.poi.model.Place

val BmeK = Place(
Expand Down Expand Up @@ -122,7 +144,7 @@ fun MapboxMap(
}

LaunchedEffect(initialStyleUri) {
map.getMapboxMap().loadStyleUri(initialStyleUri)
map.mapboxMap.loadStyleUri(initialStyleUri)
}

MapboxMapContainer(
Expand All @@ -147,29 +169,101 @@ private fun MapboxMapContainer(
onCameraChanged: (CameraState) -> Unit = {}
) {
DisposableEffect(Unit) {
val onMapLoadedListener = { _: MapLoadedEventData -> onMapFullyLoaded(map) }
map.getMapboxMap().addOnMapLoadedListener(onMapLoadedListener)
map.getMapboxMap().addOnCameraChangeListener { onCameraChanged(map.getMapboxMap().cameraState) }
onDispose { map.getMapboxMap().removeOnMapLoadedListener(onMapLoadedListener) }
val onMapLoadedListener = { _: MapLoaded -> onMapFullyLoaded(map) }
val cancelable = mutableListOf<Cancelable>()
map.mapboxMap.subscribeMapLoaded(onMapLoadedListener)
map.mapboxMap.subscribeCameraChanged { onCameraChanged(map.mapboxMap.cameraState) }
onDispose { cancelable.forEach { it.cancel() } }
}
val statusBarHeight = LocalDensity.current.run { WindowInsets.statusBars.getTop(this) }
val fixedStatusBarHeight = rememberSaveable { statusBarHeight }
AndroidView(
val fixedStatusBarHeight = rememberSaveable(statusBarHeight) { statusBarHeight }
val isMapNotSupported = LocalMapboxNotSupported.current
Crossfade(
modifier = modifier,
factory = { map }
targetState = isMapNotSupported,
label = "MapboxMap"
) { notSupported ->
if (notSupported) {
LaunchedEffect(Unit) { onMapFullyLoaded(map) }
MapsNotSupportedCard(modifier = modifier)
} else {
AndroidView(
modifier = modifier,
factory = { map }
) {
it.logo.position = 0
it.logo.marginTop = fixedStatusBarHeight.toFloat()
it.attribution.position = 0
it.attribution.marginTop = fixedStatusBarHeight.toFloat()
it.attribution.marginLeft = 240f
it.compass.marginTop = fixedStatusBarHeight.toFloat()
it.gestures.scrollEnabled = true
it.scalebar.isMetricUnits = true // TODO: set this in settings or based on location, etc.
it.scalebar.enabled = false // TODO: enable it later if needed (though pay attention to ugly design)
}
}
}
}

@Composable
fun MapsNotSupportedCard(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
it.logo.position = 0
it.logo.marginTop = fixedStatusBarHeight.toFloat()
it.attribution.position = 0
it.attribution.marginTop = fixedStatusBarHeight.toFloat()
it.attribution.marginLeft = 240f
it.compass.marginTop = fixedStatusBarHeight.toFloat()
it.gestures.scrollEnabled = true
it.scalebar.isMetricUnits = true // TODO: set this in settings or based on location, etc.
it.scalebar.enabled = false // TODO: enable it later if needed (though pay attention to ugly design)
Card(
modifier = Modifier.padding(horizontal = 16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
modifier = Modifier.size(64.dp),
imageVector = Icons.Rounded.BrokenImage,
contentDescription = "Mapbox Map Error Icon",
tint = MaterialTheme.colorScheme.onSurface
)
Text(
text = stringResource(R.string.mapbox_map_initialization_problem),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center
)
Icon(
modifier = Modifier.size(32.dp),
imageVector = Icons.Rounded.ArrowDownward,
contentDescription = "Mapbox Map Error Icon",
tint = MaterialTheme.colorScheme.onSurface
)
Text(
text = stringResource(R.string.mapbox_map_unsupported_opengl),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier.padding(top = 8.dp),
text = stringResource(R.string.mapbox_map_problem_not_affecting_jay),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface
)
}
}
}
}

@PreviewAll
@Composable
fun PreviewMapsNotSupportedCard() {
MapsNotSupportedCard()
}

fun LocationComponentPlugin.turnOnWithDefaultPuck() {
if (!enabled) {
enabled = true
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/illyan/jay/util/MapboxExceptionHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2024 Balázs Püspök-Kiss (Illyan)
*
* Jay is a driver behaviour analytics app.
*
* This file is part of Jay.
*
* Jay is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
* Jay is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Jay.
* If not, see <https://www.gnu.org/licenses/>.
*/

package illyan.jay.util

import timber.log.Timber

class MapboxExceptionHandler : Thread.UncaughtExceptionHandler {

var openGlNotSupportedCallback: () -> Unit = {}

override fun uncaughtException(t: Thread, e: Throwable) {
if (e is IllegalStateException &&
e.message?.contains("OpenGL ES 3.0 context could not be created") == true) {
// Older emulated Android devices may not support OpenGL ES 3.0 properly
openGlNotSupportedCallback()
}
Timber.e(e)
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@
<string name="download">Download</string>
<string name="refresh">Refresh</string>
<string name="no_available_models_found">No Available Models Found</string>
<string name="mapbox_map_initialization_problem">Problem occurred during Mapbox Map\'s initialization</string>
<string name="mapbox_map_unsupported_opengl">OpenGL not supported</string>
<string name="mapbox_map_problem_not_affecting_jay">Jay\'s location tracking and data analytics are not affected.</string>
<string name="delete_all">Delete All</string>
<string name="restart_required">Restart Required</string>
<string name="restart_required_description">Machine Learning functionality is in Beta. Restart is required after each action to take effect.</string>
Expand Down

0 comments on commit 42b349d

Please sign in to comment.