Widget Common Questions - Geolocation in Embedded Apps
WebView Setup Guidelines
Overview
This document outlines the required configuration for iOS and Android WebViews to properly support geolocation functionality when embedding the Coinme Orchestrator Platform. Improper WebView setup is the most common cause of location-related features failing in embedded environments.
Common Issue: Geolocation Not Working in Embedded Apps
Symptoms:
- Location-dependent features (like Discover flow) fail to load or fetch data
- The map displays but location-based searches don't trigger
- Works in standalone browser but fails in your native app's WebView
Root Cause: WebViews require explicit configuration to enable the JavaScript Geolocation API (navigator.geolocation). Without proper setup, geolocation calls fail silently or throw permission errors, preventing location-dependent features from functioning.
iOS (WKWebView) Setup
Understanding iOS Geolocation in WebViews
iOS WKWebView requires both native app-level permissions AND proper WebView configuration to enable web-based geolocation. The web page uses the JavaScript Geolocation API (navigator.geolocation.getCurrentPosition()), which the WebView must be configured to support.
Required Implementation
1. Add Location Permission to Info.plist
This is mandatory. Without this entry, geolocation requests from the WebView will be silently blocked or hang indefinitely, causing loading indicators in the web application to never complete.
Open your Info.plist file and add one or both of these keys with a user-facing description:
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to find nearby cryptocurrency services and ATMs</string>Or for background location access:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to provide location-based cryptocurrency services</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to find nearby cryptocurrency services</string>Important Notes:
- The description string will be shown to users in the permission prompt
- Be specific about why you need location access
- Most apps only need "When In Use" permission
- Without this key, the app will crash when geolocation is requested
2. Request Location Permission at App Level
Your app must request location permission using CLLocationManager before the WebView can access location:
import UIKit
import WebKit
import CoreLocation
class YourViewController: UIViewController, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Setup location manager
locationManager.delegate = self
// Request location permission
// This must be done BEFORE loading the WebView content
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
setupWebView()
}
// Optional: Handle authorization changes
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
print("Location permission granted")
case .denied, .restricted:
print("Location permission denied")
case .notDetermined:
print("Location permission not determined")
@unknown default:
break
}
}
}3. Configure WKWebView
func setupWebView() {
let configuration = WKWebViewConfiguration()
let preferences = WKWebPreferences()
// Enable JavaScript (required for geolocation)
preferences.javaScriptEnabled = true
configuration.preferences = preferences
// Enable inline media playback (recommended)
configuration.allowsInlineMediaPlayback = true
// Create WebView
webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
// Load your URL
if let url = URL(string: "https://your-coinme-url.com") {
webView.load(URLRequest(url: url))
}
}4. Important: Understanding iOS Geolocation Behavior
When a webpage in WKWebView calls navigator.geolocation.getCurrentPosition():
- First time: iOS shows a permission dialog if the app has "When In Use" permission
- The dialog will show the webpage's URL (e.g., "https://your-domain.com would like to use your current location")
- User must approve this second dialog for the webpage to access location
- This permission is remembered per-origin (domain)
Note: This is standard iOS behavior for WKWebView. The webpage-level prompt is separate from the app-level permission and is a security feature to ensure users know which specific website is accessing their location.
5. Complete iOS Example
import UIKit
import WebKit
import CoreLocation
class CoinmeWebViewController: UIViewController, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Setup location manager
locationManager.delegate = self
// Request permission before setting up WebView
requestLocationPermission()
// Setup WebView
setupWebView()
}
private func requestLocationPermission() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
// First time - request permission
locationManager.requestWhenInUseAuthorization()
case .restricted, .denied:
// User has denied or restricted location access
// Consider showing an alert explaining why you need location
showLocationDisabledAlert()
case .authorizedWhenInUse, .authorizedAlways:
// Already authorized
print("Location access authorized")
@unknown default:
break
}
}
private func setupWebView() {
let configuration = WKWebViewConfiguration()
configuration.preferences.javaScriptEnabled = true
configuration.allowsInlineMediaPlayback = true
webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
// Load Coinme Orchestrator Platform
if let url = URL(string: "https://your-coinme-orchestrator-url.com") {
webView.load(URLRequest(url: url))
}
}
private func showLocationDisabledAlert() {
let alert = UIAlertController(
title: "Location Access Disabled",
message: "Please enable location access in Settings to use location-based features.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL)
}
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
print("Location authorization status changed: \(status.rawValue)")
}
}Android (WebView) Setup
Android WebView setup is more straightforward but still requires specific configuration in multiple places.
Required Implementation
1. Add Permissions to AndroidManifest.xml
Open your AndroidManifest.xml file (typically in app/src/main/AndroidManifest.xml) and add these permissions inside the <manifest> tag but outside the <application> tag:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourcompany.yourapp">
<!-- Required for WebView to load content -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Required for geolocation functionality -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<!-- Your activity configuration -->
</activity>
</application>
</manifest>Important Notes:
INTERNETpermission is required for the WebView to load any web contentACCESS_FINE_LOCATIONenables GPS-based location (most accurate)ACCESS_COARSE_LOCATIONenables network-based location (less accurate, works without GPS)- Both location permissions should be included for best compatibility
2. Request Runtime Permissions (Android 6.0+)
Starting with Android 6.0 (API level 23), you must request dangerous permissions at runtime, not just in the manifest. Location is considered a dangerous permission.
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends AppCompatActivity {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Check if location permission is already granted
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
// Permission already granted, setup WebView
setupWebView();
} else {
// Request permission
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
},
LOCATION_PERMISSION_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted, setup WebView
setupWebView();
} else {
// Permission denied
Toast.makeText(this,
"Location permission is required for location-based features",
Toast.LENGTH_LONG).show();
// You may still setup WebView, but location features won't work
setupWebView();
}
}
}
private void setupWebView() {
// Implementation in next section
}
}3. Configure WebView Settings (CRITICAL)
This is the most important step. Without these settings, geolocation will not work even if you have all permissions granted.
import android.webkit.GeolocationPermissions;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
private void setupWebView() {
webView = findViewById(R.id.webView);
// Get WebView settings
WebSettings settings = webView.getSettings();
// ==========================================
// CRITICAL SETTINGS FOR GEOLOCATION
// ==========================================
// 1. Enable JavaScript (required for web apps)
settings.setJavaScriptEnabled(true);
// 2. Enable Geolocation (MUST BE SET)
settings.setGeolocationEnabled(true);
// 3. Enable DOM Storage (required by many web apps)
settings.setDomStorageEnabled(true);
// 4. Enable Database (may be needed for geolocation caching)
settings.setDatabaseEnabled(true);
// ==========================================
// RECOMMENDED SETTINGS
// ==========================================
// Allow JavaScript to open windows
settings.setJavaScriptCanOpenWindowsAutomatically(true);
// Enable zoom controls (optional, but recommended)
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false); // Hide the zoom buttons
// Use wide viewport (recommended for responsive web apps)
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
// Enable mixed content (if your site uses HTTPS but loads some HTTP resources)
// Only use if necessary, as it reduces security
// settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
// ==========================================
// SET WEBCLIENT (handles navigation)
// ==========================================
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Load URLs within the WebView instead of external browser
view.loadUrl(url);
return true;
}
});
// ==========================================
// SET WEBCHROMECLIENT (REQUIRED FOR GEOLOCATION)
// ==========================================
// This is CRITICAL - without this, geolocation will not work
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onGeolocationPermissionsShowPrompt(
String origin,
GeolocationPermissions.Callback callback) {
// Grant permission to the webpage to use geolocation
// Since user already granted permission at the app level,
// we automatically grant it to the webpage
// Parameters:
// - origin: The origin of the webpage requesting permission
// - allow: true to grant permission
// - retain: false to not remember this decision (ask each time)
// true to remember (don't ask again for this origin)
callback.invoke(origin, true, false);
}
});
// Load the Coinme Orchestrator Platform
webView.loadUrl("https://your-coinme-orchestrator-url.com");
}4. Handle WebView Lifecycle
Add these methods to properly handle the back button and WebView lifecycle:
@Override
public void onBackPressed() {
// Allow user to navigate back in WebView history
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.destroy();
}
super.onDestroy();
}5. Complete Android Example
Here's a complete working example:
package com.yourcompany.yourapp;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.webkit.GeolocationPermissions;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends AppCompatActivity {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Find WebView in layout
webView = findViewById(R.id.webView);
// Check and request location permission
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
setupWebView();
} else {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
},
LOCATION_PERMISSION_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setupWebView();
} else {
Toast.makeText(this,
"Location permission is required for location features",
Toast.LENGTH_LONG).show();
setupWebView(); // Still setup, but location won't work
}
}
}
private void setupWebView() {
WebSettings settings = webView.getSettings();
// CRITICAL: Enable these for geolocation to work
settings.setJavaScriptEnabled(true);
settings.setGeolocationEnabled(true);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
// Recommended settings
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
// Handle navigation within WebView
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
// CRITICAL: Handle geolocation permission requests from webpage
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onGeolocationPermissionsShowPrompt(
String origin,
GeolocationPermissions.Callback callback) {
// Automatically grant permission since user approved at app level
callback.invoke(origin, true, false);
}
});
// Load the Coinme Orchestrator Platform
webView.loadUrl("https://your-coinme-orchestrator-url.com");
}
@Override
public void onBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.destroy();
}
super.onDestroy();
}
}6. Layout File (activity_main.xml)
Your layout should include the WebView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>Testing Your Implementation
Create a Test Page
Use this simple HTML page to verify geolocation is working before testing with the Coinme Orchestrator Platform:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Geolocation Test</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
button {
background: #007AFF;
color: white;
border: none;
padding: 15px 30px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
margin: 10px 0;
}
button:active {
background: #0051D5;
}
#result {
margin-top: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
word-wrap: break-word;
}
.success {
color: green;
}
.error {
color: red;
}
</style>
</head>
<body>
<h1>Geolocation Test</h1>
<p>Click the button below to test if geolocation is working in your WebView.</p>
<button onclick="testGeolocation()">Test Geolocation</button>
<div id="result"></div>
<script>
function testGeolocation() {
const result = document.getElementById('result');
// Check if geolocation is supported
if (!navigator.geolocation) {
result.innerHTML = '<p class="error"><strong>Error:</strong> Geolocation is not supported by this browser/WebView.</p>';
return;
}
result.innerHTML = '<p>Requesting location...</p>';
// Request current position
navigator.geolocation.getCurrentPosition(
// Success callback
function(position) {
result.innerHTML = `
<h2 class="success">✓ Success!</h2>
<p><strong>Latitude:</strong> ${position.coords.latitude}</p>
<p><strong>Longitude:</strong> ${position.coords.longitude}</p>
<p><strong>Accuracy:</strong> ${position.coords.accuracy} meters</p>
<p><strong>Timestamp:</strong> ${new Date(position.timestamp).toLocaleString()}</p>
`;
},
// Error callback
function(error) {
let errorMessage = '';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMessage = 'User denied the request for geolocation.';
break;
case error.POSITION_UNAVAILABLE:
errorMessage = 'Location information is unavailable.';
break;
case error.TIMEOUT:
errorMessage = 'The request to get user location timed out.';
break;
case error.UNKNOWN_ERROR:
errorMessage = 'An unknown error occurred.';
break;
}
result.innerHTML = `
<h2 class="error">✗ Error</h2>
<p><strong>Code:</strong> ${error.code}</p>
<p><strong>Message:</strong> ${errorMessage}</p>
`;
},
// Options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
}
</script>
</body>
</html>Verification Checklist
Use this checklist to verify your implementation:
iOS:
-
NSLocationWhenInUseUsageDescription(orNSLocationAlwaysAndWhenInUseUsageDescription) is present in Info.plist - App requests location permission using
CLLocationManager.requestWhenInUseAuthorization() - WKWebView has JavaScript enabled (
preferences.javaScriptEnabled = true) - User sees native iOS permission dialog when app first launches
- User sees webpage permission dialog when geolocation is first accessed in WebView
- Test page successfully displays latitude/longitude
Android:
-
ACCESS_FINE_LOCATIONandACCESS_COARSE_LOCATIONpermissions in AndroidManifest.xml -
INTERNETpermission in AndroidManifest.xml - Runtime permission request implemented for Android 6.0+
-
setJavaScriptEnabled(true)called on WebView settings -
setGeolocationEnabled(true)called on WebView settings -
setDomStorageEnabled(true)called on WebView settings - WebChromeClient is set with
onGeolocationPermissionsShowPromptoverride - User sees permission dialog when app first launches
- Test page successfully displays latitude/longitude
Both Platforms:
- Test page loads successfully in WebView
- Clicking "Test Geolocation" button shows success message with coordinates
- Coinme Orchestrator Platform loads correctly
- Location-based features (like Discover flow) work as expected
Troubleshooting
iOS Issues
Problem: App crashes when geolocation is requested
- Solution: Verify
NSLocationWhenInUseUsageDescriptionis in Info.plist. This is mandatory.
Problem: Geolocation times out or fails with "permission denied"
- Possible causes:
- User denied location permission at app level → Check Settings > Your App > Location
- User denied location permission at webpage level → User needs to clear website data and try again
- Location services are disabled on device → Check Settings > Privacy > Location Services
Problem: Permission prompt never appears
- Solution: Ensure
locationManager.requestWhenInUseAuthorization()is called before loading WebView content
Problem: Second permission prompt (webpage-level) never appears
- Solution: This is expected behavior for WKWebView. The webpage prompt should appear automatically when
navigator.geolocation.getCurrentPosition()is called for the first time. If it doesn't appear, JavaScript may be disabled or there may be an error in the web page code.
Android Issues
Problem: Geolocation fails with "User denied Geolocation"
- Possible causes:
- WebChromeClient is not set → Verify
setWebChromeClient()is called onGeolocationPermissionsShowPromptis not overridden → Verify the override existscallback.invoke()is not called → Verify you're callingcallback.invoke(origin, true, false)
- WebChromeClient is not set → Verify
Problem: JavaScript console shows "navigator.geolocation is undefined"
- Solution: Ensure
setJavaScriptEnabled(true)andsetGeolocationEnabled(true)are both called
Problem: Runtime permission dialog never appears
- Solution:
- Check that your
targetSdkVersionis 23 or higher - Verify permission check and request code is being executed
- Ensure permissions are declared in AndroidManifest.xml
- Check that your
Problem: WebView shows blank screen
- Possible causes:
- Missing
INTERNETpermission in manifest - JavaScript is not enabled
- Network connectivity issues
- Invalid URL
- Missing
Problem: Test page shows "Position unavailable" error
- Solution:
- Verify device GPS is enabled
- Ensure device has network connectivity (needed for assisted GPS)
- Test outside in clear view of sky if indoors
- Try on a different device to rule out hardware issues
General Issues
Problem: Works in mobile browser but not in WebView
- Solution: Compare your WebView configuration against this guide point-by-point. Most likely JavaScript or geolocation is not enabled in WebView settings.
Problem: Map shows location but Discover flow doesn't load results
- Explanation: The map may use IP-based geolocation as a fallback. Verify that the JavaScript Geolocation API actually works using the test page. The Discover flow likely requires the more accurate GPS-based location from
navigator.geolocation.
Problem: Works on one platform but not the other
- Solution: Follow the platform-specific checklist above to identify missing configuration steps.
Problem: Works first time but not after denying permission
- Solution:
- iOS: User must clear website data or reset location permissions in Settings > Safari > Advanced > Website Data
- Android: User must clear app data or reinstall the app to reset the webpage-level permission
Security Considerations
Best Practices
- Only request "When In Use" permission unless you genuinely need background location access
- Be transparent about why you need location access in your permission description
- Handle permission denial gracefully with clear user messaging
- Don't request permission unnecessarily - only request when user attempts to use a location feature
- Respect user privacy - only use location data for stated purposes
Android Manifest Best Practices
<!-- Use ACCESS_COARSE_LOCATION if you don't need precise GPS -->
<!-- For cryptocurrency ATM location, ACCESS_FINE_LOCATION is appropriate -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Don't request background location unless truly needed -->
<!-- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> -->WebChromeClient Security Note
The example code automatically grants geolocation permission to any website loaded in the WebView:
callback.invoke(origin, true, false);If you load multiple different websites in your WebView, you may want to check the origin first:
@Override
public void onGeolocationPermissionsShowPrompt(
String origin,
GeolocationPermissions.Callback callback) {
// Only grant permission to your specific domain
if (origin.startsWith("https://your-coinme-domain.com")) {
callback.invoke(origin, true, false);
} else {
callback.invoke(origin, false, false);
}
}Additional Resources
Documentation Links
- iOS CLLocationManager Documentation
- iOS WKWebView Documentation
- Android WebView Documentation
- Android Geolocation Permissions
- MDN Geolocation API Reference
Common Error Codes
JavaScript Geolocation Errors:
PERMISSION_DENIED (1)- User denied permissionPOSITION_UNAVAILABLE (2)- Unable to determine positionTIMEOUT (3)- Request timed out
Updated 10 days ago