Usage guidelines
Legal requirements
Some features, such as police and speed camera reports, may be illegal in certain countries. Ensure that the safety overlay and the corresponding entries in socialReports overlay are disabled where applicable. Additionally, restrict the availability of the SocialOverlay features based on current local regulations.
The Magic Lane Android SDK utilizes data from OpenStreetMap. Please ensure proper attribution is given in accordance with the OpenStreetMap license.
Any use or display of Wikipedia content must include appropriate attribution, as outlined in the Reusers' rights and obligations section of Wikipedia's copyright guidelines.
SDK thread
The Maps SDK for Android is designed to be thread-safe. Most operations must be performed on specific threads to ensure optimal performance and avoid potential issues.
You can interact with SDK objects only from the SDK thread. The SDK thread is initialized when the SDK is initialized and runs in the background. You can use the SDKCall.execute method to run code on the SDK thread.
- Kotlin
- Java
SDKCall.execute {
// Your code here
}
SDKCall.execute(() -> {
// Your code here
});
This ensures that the code inside the block runs on the SDK thread, allowing safe interaction with SDK objects.
If you perform SDK operations from a different thread the sdk will throw an exception:
java.lang.Exception: Please ensure that your call is placed on GEMSdkThread or GLThread. See GemCall.execute{ ... }!
Units of measurement
The SDK primarily uses SI units for measurements, including:
- Meter (distance).
- Second (time).
- Kilogram (mass).
- Meters per second (speed).
- Watts for power (consumption).
The length value and measurement unit used in TTS (text-to-speech) instructions, such as "After 150 meters, bear left," are determined by the unit system configured through the SDKSettings.unitSystem setter.
In certain cases, other units of measurement are used as they are better suited. For instance, the height, length and width fields of the TruckProfile class are measured in centimeters. These exceptions are clearly indicated in the API reference.
Error handling
The Maps SDK for Android uses a standardized error handling system through the GemError class. All SDK operations return error codes that indicate the success or failure of the requested operation.
Understanding Error Codes
Error codes are represented as integers (type alias ErrorCode = Int). The SDK provides various error constants in the GemError object:
GemError.NoError(0): Operation completed successfullyGemError.Cancel(-3): Operation was canceled (not considered an error)- Negative values: Indicate various error conditions
Checking for Errors
Use GemError.isError() to determine if an error code represents an actual error:
- Kotlin
- Java
val errorCode = someSDKOperation()
if (GemError.isError(errorCode)) {
// Handle the error
val errorMessage = GemError.getMessage(errorCode, context)
Log.e("SDK", "Operation failed: $errorMessage")
} else {
// Operation succeeded or was canceled
}
int errorCode = someSDKOperation();
if (GemError.isError(errorCode)) {
// Handle the error
String errorMessage = GemError.getMessage(errorCode, context);
Log.e("SDK", "Operation failed: " + errorMessage);
} else {
// Operation succeeded or was canceled
}
Getting Error Messages
The GemError.getMessage() method provides human-readable error descriptions:
- Kotlin
- Java
val errorMessage = GemError.getMessage(errorCode, context)
String errorMessage = GemError.getMessage(errorCode, context);
Common Error Types
The SDK defines several categories of errors:
File and I/O Errors:
GemError.Io(-6): General I/O errorGemError.AccessDenied(-7): Access denied to resourceGemError.NotFound(-11): Required item not foundGemError.NoDiskSpace(-9): Insufficient disk space
Network Errors:
GemError.NoConnection(-24): No network connection availableGemError.NetworkTimeout(-29): Network operation timed outGemError.NetworkFailed(-23): Network connection failedGemError.ConnectionRequired(-25): Network connection required
Routing and Navigation Errors:
GemError.NoRoute(-18): No route could be calculatedGemError.WaypointAccess(-19): One or more waypoints not accessibleGemError.RouteTooLong(-20): Route is too long, use intermediate waypoints
Resource and Memory Errors:
GemError.NoMemory(-14): Insufficient memoryGemError.ResourceMissing(-36): Required resources missing, reinstall neededGemError.InvalidInput(-15): Invalid input provided
Handling Asynchronous Operations
Many SDK operations are asynchronous and use listeners to report completion. Use ProgressListener to handle errors in async operations:
- Kotlin
- Java
val listener = ProgressListener.create(
onCompleted = { errorCode, hint ->
if (GemError.isError(errorCode)) {
val errorMessage = GemError.getMessage(errorCode, this)
// Handle error appropriately
showErrorDialog("Operation failed: $errorMessage")
} else {
// Handle successful completion
onOperationSuccess()
}
}
)
ProgressListener listener = ProgressListener.create(
(errorCode, hint) -> {
if (GemError.isError(errorCode)) {
String errorMessage = GemError.getMessage(errorCode, this);
// Handle error appropriately
showErrorDialog("Operation failed: " + errorMessage);
} else {
// Handle successful completion
onOperationSuccess();
}
}
);
Best Practices
- Always check error codes: Never assume operations succeed without checking the return value
- Provide user feedback: Convert technical error codes to user-friendly messages
- Handle network errors gracefully: Implement retry logic for network-related operations
- Log errors for debugging: Include error codes and messages in your application logs
- Handle specific error types: Implement different handling strategies for different error categories
- Kotlin
- Java
// Example: Comprehensive error handling
fun handleSDKError(errorCode: ErrorCode, context: Context) {
when (errorCode) {
GemError.NoError -> {
// Success - no action needed
}
GemError.Cancel -> {
// Operation was canceled by user
showMessage("Operation canceled")
}
GemError.NoConnection,
GemError.NetworkFailed,
GemError.NetworkTimeout -> {
// Network-related errors
showRetryDialog("Network error. Please check your connection and try again.")
}
GemError.NoRoute -> {
// Routing specific error
showMessage("Could not calculate route. Please check your destination.")
}
GemError.ResourceMissing -> {
// Critical error requiring app reinstall
showMessage("App resources are missing. Please reinstall the application.")
}
else -> {
// Generic error handling
val message = GemError.getMessage(errorCode, context)
showMessage("Error: $message")
}
}
}
// Example: Comprehensive error handling
public void handleSDKError(int errorCode, Context context) {
if (errorCode == GemError.NoError) {
// Success - no action needed
} else if (errorCode == GemError.Cancel) {
// Operation was canceled by user
showMessage("Operation canceled");
} else if (errorCode == GemError.NoConnection
|| errorCode == GemError.NetworkFailed
|| errorCode == GemError.NetworkTimeout) {
// Network-related errors
showRetryDialog("Network error. Please check your connection and try again.");
} else if (errorCode == GemError.NoRoute) {
// Routing specific error
showMessage("Could not calculate route. Please check your destination.");
} else if (errorCode == GemError.ResourceMissing) {
// Critical error requiring app reinstall
showMessage("App resources are missing. Please reinstall the application.");
} else {
// Generic error handling
String message = GemError.getMessage(errorCode, context);
showMessage("Error: " + message);
}
}
Do not extend the classes provided by the SDK
The SDK is designed to deliver all necessary functionalities in an intuitive and straightforward manner. Avoid extending any of the Maps SDK for Android classes. Use the callback methods provided by the SDK for seamless integration.
DateTime Parameter Rules
Make sure the DateTime passed to method is a valid positive utc value. Values below the DateTime.utc(0) are not supported.
General principles related to listeners
- Use the provided register methods or constructor parameters to subscribe to events.
- Only one callback can be active per event type. If multiple callbacks are registered for the same event type, the most recently registered callback will override the previous one.
- To unsubscribe from events, register an empty callback.
Provide SDK logs when reporting issues
The logs may contain sensitive information, so review them before sharing publicly.
Collecting SDK logs and crash reports
The SDK automatically generates logs and crash reports that are essential for diagnosing issues. To access and send these logs:
Automatic log collection
The SDK automatically creates log files at runtime. You can access these logs using the GemSdk.appLogPath property, which returns the path to the current application log file.
Crash reports
If the application crashes, crash logs are automatically stored in the SDK's internal storage path under GMcrashlogs/last/. You can access the internal storage path using GemSdk.internalStoragePath.
Example implementation
Here's how to programmatically collect and send debug information (based on the SendDebugInfo example):
- Kotlin
- Java
private fun sendDebugInfo() {
// Get SDK version for subject line
var subject = ""
SdkCall.execute {
subject = GemSdk.sdkVersion?.let {
String.format("User feedback (SDK) - %d.%d.%d.%d.%s",
it.major, it.minor, it.year, it.week, it.revision)
} ?: "User feedback"
}
// Create email intent
val sendIntent = Intent(Intent.ACTION_SEND_MULTIPLE)
sendIntent.type = "message/rfc822"
sendIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf("support@magicearth.com"))
sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject)
val uris = ArrayList<Uri>()
// Attach application logs
val privateLogPath = GemSdk.appLogPath
privateLogPath?.let { logPath ->
val publicPath = GemUtil.getApplicationPublicFilesAbsolutePath(this, "phoneLog.txt")
if (GemUtil.copyFile(logPath, publicPath)) {
val file = File(publicPath)
file.deleteOnExit()
try {
uris.add(FileProvider.getUriForFile(this, packageName + ".provider", file))
} catch (e: Exception) {
GEMLog.error(this, "Error attaching log file: ${e.message}")
}
}
}
// Attach crash reports if available
if (GemSdk.internalStoragePath.isNotEmpty()) {
val crashPath = "${GemSdk.internalStoragePath}/GMcrashlogs/last"
val crashDir = File(crashPath)
if (crashDir.exists() && crashDir.isDirectory) {
crashDir.listFiles()?.firstOrNull()?.let { crashFile ->
try {
uris.add(FileProvider.getUriForFile(this, packageName + ".provider", crashFile))
} catch (e: Exception) {
GEMLog.error(this, "Error attaching crash file: ${e.message}")
}
}
}
}
sendIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
startActivity(sendIntent)
}
private void sendDebugInfo() {
// Get SDK version for subject line
final String[] subject = {""};
SdkCall.execute(() -> {
SdkVersion version = GemSdk.getSdkVersion();
if (version != null) {
subject[0] = String.format("User feedback (SDK) - %d.%d.%d.%d.%s",
version.getMajor(), version.getMinor(), version.getYear(),
version.getWeek(), version.getRevision());
} else {
subject[0] = "User feedback";
}
});
// Create email intent
Intent sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
sendIntent.setType("message/rfc822");
sendIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"support@magicearth.com"});
sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject[0]);
ArrayList<Uri> uris = new ArrayList<>();
// Attach application logs
String privateLogPath = GemSdk.getAppLogPath();
if (privateLogPath != null) {
String publicPath = GemUtil.getApplicationPublicFilesAbsolutePath(this, "phoneLog.txt");
if (GemUtil.copyFile(privateLogPath, publicPath)) {
File file = new File(publicPath);
file.deleteOnExit();
try {
uris.add(FileProvider.getUriForFile(this, getPackageName() + ".provider", file));
} catch (Exception e) {
GEMLog.error(this, "Error attaching log file: " + e.getMessage());
}
}
}
// Attach crash reports if available
if (!GemSdk.getInternalStoragePath().isEmpty()) {
String crashPath = GemSdk.getInternalStoragePath() + "/GMcrashlogs/last";
File crashDir = new File(crashPath);
if (crashDir.exists() && crashDir.isDirectory()) {
File[] crashFiles = crashDir.listFiles();
if (crashFiles != null && crashFiles.length > 0) {
try {
uris.add(FileProvider.getUriForFile(this, getPackageName() + ".provider", crashFiles[0]));
} catch (Exception e) {
GEMLog.error(this, "Error attaching crash file: " + e.getMessage());
}
}
}
}
sendIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
startActivity(sendIntent);
}
Required Android manifest configuration
To share log files, you need to configure a FileProvider in your AndroidManifest.xml:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
And create a res/xml/provider_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="external_files" path="." />
<files-path name="files" path="." />
<cache-path name="cache" path="." />
</paths>
Adding debug logs to your code
You can add custom debug messages to help with troubleshooting:
- Kotlin
- Java
GEMLog.debug(this, "This is a debug message!")
GEMLog.error(this, "This is an error message!")
GEMLog.debug(this, "This is a debug message!");
GEMLog.error(this, "This is an error message!");
Other useful information
When reporting issues, please provide the following information to help us diagnose and resolve the problem effectively:
- Clear and concise description of the bug or issue.
- Steps to reproduce the issue, including a minimal code sample if possible.
- Expected behavior versus actual behavior you are experiencing.
- Screenshots or videos to help visualize the issue (if applicable).
- SDK version you are using (obtainable via
GemSdk.sdkVersion). - Platform details (e.g., iOS/Android, OS version, device model).
- SDK logs and crash reports (using the method described above).
- Geographical location where the issue occurred, if relevant (for example, routing, navigation, or search-related issues).
- Any other relevant information that could assist us in diagnosing and resolving the issue.
- Additionally, ensure that your SDK is up-to-date, as newer versions often include bug fixes and performance improvements.