Skip to content

Commit 2d3341a

Browse files
RD-1209: Update map device loading (#59)
1 parent c711875 commit 2d3341a

File tree

3 files changed

+79
-35
lines changed

3 files changed

+79
-35
lines changed

MapTilerSDK/src/main/java/com/maptiler/maptilersdk/bridge/WebViewExecutor.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package com.maptiler.maptilersdk.bridge
88

99
import android.content.Context
10+
import android.view.View
1011
import android.view.ViewGroup
1112
import android.webkit.WebChromeClient
1213
import android.webkit.WebSettings
@@ -52,6 +53,8 @@ internal class WebViewExecutor(
5253
}
5354

5455
private var _webView: WebView? = null
56+
private var hasLoadedContent: Boolean = false
57+
private var jsInterfaceAdded: Boolean = false
5558
internal val webView: WebView
5659
get() {
5760
if (_webView == null) {
@@ -69,6 +72,8 @@ internal class WebViewExecutor(
6972
WebView(context).apply {
7073
settings.javaScriptEnabled = true
7174
settings.allowFileAccess = true
75+
settings.allowFileAccessFromFileURLs = true
76+
settings.allowUniversalAccessFromFileURLs = true
7277
settings.domStorageEnabled = true
7378
settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
7479
settings.useWideViewPort = true
@@ -93,13 +98,55 @@ internal class WebViewExecutor(
9398
ViewGroup.LayoutParams.MATCH_PARENT,
9499
)
95100

96-
loadUrl("file:///android_asset/${Constants.JSResources.MAPTILER_MAP}.${Constants.JSResources.HTML_EXTENSION}")
101+
// Defer loading until JS interface is attached and the view is attached.
102+
// This prevents early page load causing "Android is not defined" on some devices.
103+
if (isAttachedToWindow) {
104+
ensureContentLoadedIfReady(this)
105+
} else {
106+
addOnAttachStateChangeListener(
107+
object : View.OnAttachStateChangeListener {
108+
override fun onViewAttachedToWindow(v: View) {
109+
ensureContentLoadedIfReady(this@apply)
110+
removeOnAttachStateChangeListener(this)
111+
}
112+
113+
override fun onViewDetachedFromWindow(v: View) = Unit
114+
},
115+
)
116+
}
97117
}
98118
}
99119
}
100120

101121
internal fun setWebView(webView: WebView) {
102122
this._webView = webView
123+
// Ensure our clients are attached so navigation callbacks work consistently.
124+
webView.webChromeClient = WebChromeClient()
125+
webView.webViewClient =
126+
object : WebViewClient() {
127+
override fun onPageFinished(
128+
view: WebView?,
129+
url: String?,
130+
) {
131+
url?.let { delegate?.onNavigationFinished(it) }
132+
}
133+
}
134+
135+
// If provided WebView is already attached, try loading if ready.
136+
if (webView.isAttachedToWindow) {
137+
ensureContentLoadedIfReady(webView)
138+
} else {
139+
webView.addOnAttachStateChangeListener(
140+
object : View.OnAttachStateChangeListener {
141+
override fun onViewAttachedToWindow(v: View) {
142+
ensureContentLoadedIfReady(webView)
143+
webView.removeOnAttachStateChangeListener(this)
144+
}
145+
146+
override fun onViewDetachedFromWindow(v: View) = Unit
147+
},
148+
)
149+
}
103150
}
104151

105152
internal fun getAttachableWebView(): WebView? = webView
@@ -151,9 +198,21 @@ internal class WebViewExecutor(
151198

152199
internal fun addJSInterface(jsInterface: MTJavaScriptInterface) {
153200
webView.addJavascriptInterface(jsInterface, "Android")
201+
jsInterfaceAdded = true
202+
// Attempt to load content now that the interface is ready.
203+
ensureContentLoadedIfReady(webView)
154204
}
155205

156206
internal fun reload() {
157207
webView.reload()
158208
}
209+
210+
private fun ensureContentLoadedIfReady(webView: WebView) {
211+
if (!hasLoadedContent && jsInterfaceAdded && webView.isAttachedToWindow) {
212+
hasLoadedContent = true
213+
webView.loadUrl(
214+
"file:///android_asset/${Constants.JSResources.MAPTILER_MAP}.${Constants.JSResources.HTML_EXTENSION}",
215+
)
216+
}
217+
}
159218
}

MapTilerSDK/src/main/java/com/maptiler/maptilersdk/map/MTMapViewClassic.kt

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,16 @@ package com.maptiler.maptilersdk.map
99
import android.content.Context
1010
import android.util.AttributeSet
1111
import android.view.LayoutInflater
12-
import android.webkit.WebChromeClient
1312
import android.webkit.WebSettings
1413
import android.webkit.WebView
15-
import android.webkit.WebViewClient
1614
import android.widget.FrameLayout
1715
import com.maptiler.maptilersdk.R
1816
import com.maptiler.maptilersdk.bridge.MTBridge
19-
import com.maptiler.maptilersdk.bridge.WebViewExecutor.Constants
20-
import com.maptiler.maptilersdk.logging.MTLogType
21-
import com.maptiler.maptilersdk.logging.MTLogger
2217
import com.maptiler.maptilersdk.map.style.MTMapReferenceStyle
2318
import com.maptiler.maptilersdk.map.style.MTMapStyleVariant
2419
import com.maptiler.maptilersdk.map.style.MTStyle
2520
import kotlinx.coroutines.CoroutineScope
2621
import kotlinx.coroutines.Dispatchers
27-
import kotlinx.coroutines.launch
2822

2923
class MTMapViewClassic(
3024
context: Context,
@@ -59,45 +53,30 @@ class MTMapViewClassic(
5953
this._controller = controller
6054
this._styleVariant = styleVariant
6155

62-
_controller.bind(scope)
63-
_controller.options = options
64-
65-
val style = MTStyle(referenceStyle, styleVariant)
66-
_controller.style = style
67-
56+
// Inflate and bind WebView first so the executor manages loading and callbacks.
6857
val rootView = LayoutInflater.from(context).inflate(R.layout.mtmapview_layout, this, true)
6958
webView = rootView.findViewById(R.id.map)
59+
_controller.setWebView(webView)
7060

61+
// Configure settings only (clients + loading are handled by the executor).
7162
webView.apply {
7263
settings.javaScriptEnabled = true
7364
settings.allowFileAccess = true
65+
settings.allowFileAccessFromFileURLs = true
66+
settings.allowUniversalAccessFromFileURLs = true
7467
settings.domStorageEnabled = true
7568
settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
7669
settings.useWideViewPort = true
7770
settings.loadWithOverviewMode = true
7871

7972
isVerticalScrollBarEnabled = false
80-
81-
webChromeClient = WebChromeClient()
82-
webViewClient =
83-
object : WebViewClient() {
84-
override fun onPageFinished(
85-
view: WebView?,
86-
url: String?,
87-
) {
88-
scope.launch(Dispatchers.Main) {
89-
try {
90-
_controller.initializeMap()
91-
} catch (error: Exception) {
92-
MTLogger.log("Map Init error $error", MTLogType.ERROR)
93-
}
94-
}
95-
}
96-
}
97-
98-
loadUrl("file:///android_asset/${Constants.JSResources.MAPTILER_MAP}.${Constants.JSResources.HTML_EXTENSION}")
9973
}
10074

101-
_controller.setWebView(webView)
75+
// Now bind the controller (attaches JS interface) and set options/style.
76+
_controller.bind(scope)
77+
_controller.options = options
78+
79+
val style = MTStyle(referenceStyle, styleVariant)
80+
_controller.style = style
10281
}
10382
}

MapTilerSDK/src/test/java/com/maptiler/maptilersdk/StyleAndCommandsTests.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ class StyleAndCommandsTests {
7575

7676
@Test fun addLayer_Symbol_NoIcon_AddsLayerDirectly() {
7777
val layer = MTSymbolLayer("sym1", "src1")
78-
val js = com.maptiler.maptilersdk.commands.style.AddLayer(layer).toJS()
78+
val js =
79+
com.maptiler.maptilersdk.commands.style
80+
.AddLayer(layer)
81+
.toJS()
7982
assertEquals("${MTBridge.MAP_OBJECT}.addLayer({\"id\":\"sym1\",\"type\":\"symbol\",\"source\":\"src1\"});", js)
8083
}
8184

@@ -89,7 +92,10 @@ class StyleAndCommandsTests {
8992
every { ImageHelper.encodeImageWithMime(any()) } returns EncodedImage("AAA", "image/png")
9093

9194
val layer = MTSymbolLayer("sym2", "src2", bmp)
92-
val js = com.maptiler.maptilersdk.commands.style.AddLayer(layer).toJS()
95+
val js =
96+
com.maptiler.maptilersdk.commands.style
97+
.AddLayer(layer)
98+
.toJS()
9399

94100
// Check presence of image add and addLayer call and data URL with correct mime
95101
assertTrue(js.contains("addImage('iconsym2'"))

0 commit comments

Comments
 (0)