Core
This module is required to use almost all other modules. It handles the BeNomad license validation, the download/deployment of the maps and other required resources. To use it, you need a valid purchase UUID key provided by BeNomad. This unique key will determine which maps (HERE/TomTom/OpenStreetMap..etc.) have to be downloaded and how many installations are allowed. It also define the maps deployment mode (full maps or hybrid maps) and the map coverage.
Almost all modules depends on this module :
Map module
Map Services module
Geocoder module
Planner module
Navigation module
GPS module
Notes: For the first Core initialization, the device must have an Internet connection. Then, the Core must be initialized with an Internet connection before the end of "authorized offline period" (default duration is 30 days). Check The OnOfflinePeriodExpiredCallback to be notified when the offline period has expired for further details.
Module Core
The
OnInitProgressCallbackto get the Core initialization progressThe
OnMapDownloadProgressCallbackto get the progress of the maps downloadThe
OnHybridMapDownloadProgressCallbackto get the progress of the hybrid maps downloadThe
OnMapExtractionProgressCallbackto get the progress of the maps extractionThe
OnLicenseErrorCallbackto be notified about errors with the licenseThe
OnLicenseExpiredCallbackto be notified when the license has expiredThe
OnMapUpdateAvailableCallbackto be notified when a map update is availableThe
OnOfflinePeriodExpiredCallbackto be notified when the offline period has expired
Dependencies
This module depends on other BeNomad's modules :
Error Manager
Other module's dependencies :
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "com.google.android.material:material:1.8.0"
implementation "androidx.core:core-ktx:1.9.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1"Permissions
On Android platforms < 10 and before starting the module initialization, your Android application must request the READ_PHONE_STATE mandatory permission (It is required for the licensing validation process).
Optionally, the WRITE_EXTERNAL_STORAGE permission may be required if maps have to be deployed outside of the application's scoped storage (only possible for Android platforms < 11).
The application must also declare the INTERNET permission in its Manifest.
We simplified this process in the Permissions Manager module. You can see how to use it in its own documentation. It is not mandatory to use this module.
Core initialization
Here is a sample code that shows how the Core can be initialized :
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
import com.benomad.msdk.core.callbacks.OnCoreInit.OnCoreInitCallback
import com.benomad.msdk.core.Core
// Callback used to get the result of the Core initialization
val coreInitCallback = object : OnCoreInitCallback{
override fun onCoreReady() {
//Core initialization is successful
}
override fun onCoreInitError(error: Error) {
//An error occurred during the Core initialization
}
override fun onCoreInitException(exception: Exception) {
//An exception was raised during the Core initialization
}
}
// The READ_PHONE_STATE permission must have been accepted by the user (if the Android version < 10)
//internally launched in a coroutine
Core.getInstance().init(
applicationContext,
purchaseUUID,
mapsDeploymentPath,
resourcesPathInAssets,
mapsPathInAssets,
coreInitCallback
)Here is a description of the init method's arguments :
``applicationContext``: the application context``purchaseUUID``: A BeNomad purchase UUID``mapsDeploymentPath``: the absolute path to deploy the maps to. If the maps are already deployed in the given path, it will be considered as already deployed (the maps won't be downloaded). If no value is passed for this argument, the maps are deployed in a "Maps" folder in the external scoped storage (the "files" folder of the application)``resourcesPathInAssets``: the relative path from the assets folder to the resources folder. Pass null if you don't need to deploy the resources (required to use the Map module)``mapsPathInAssets``: the relative path from the assets to the maps folder to deploy. If you pass null, the maps will be automatically downloaded from the BeNomad servers if no maps resources are found in the given``mapsDeploymentPath````callback``: the``OnCoreInitCallback ``observer that will notify about the Core init result
Please note that specifying a mapsDeploymentPath that is outside of the application's scoped storage only works for versions of Android < 11. You will also need to request the WRITE_EXTERNAL_STORAGE permission before initializing the Core in that case.
We do recommend to use default value for mapsDeploymentPath which is the external scoped storage of the application (in the internal memory of the device). You can get this value with Core.getDefaultMapsPath method.
If you need to deploy map data on an SD card, you need to do it manually before initializing the Core because automatic online map data deployment on an SD card is currently not supported. Check manual update or initial deployment section for further details.
Map data deployment
Automatic online deployment
If no map data is deployed before initializing the Core or during the Core intialization (using mapsPathInAssets argument of the Core.init method), data will automatically be deployed if the device has an internet connection. Note: automatic online map data deployment on an SD card is currently not supported.
For a complete, step-by-step explanation of the whole map update process (the order in which the callbacks fire, what happens automatically, and what your application must do), see The map update process in detail below.
Here is how to handle online map data updates :
To be notified about an available map data update, check The
OnMapUpdateAvailableCallbackto be notified when a map update is availableAfter accepting the update, you can use The
OnMapDownloadProgressCallbackto get the progress of the maps downloadTo check if any error occurs during the download, use The
OnMapDownloadErrorCallbackto be notified when an error occurred during the map download, whether it's the original download or an updateTo be notified when the download has finished, use The
OnMapDownloadedCallbackto be notified when the map update is downloaded, or when the initial map data is downloaded
After OnMapDownloadedCallback has been called, the downloaded update is staged and is applied automatically the next time you initialize the Core (at your next app launch for example, it depends on your implementation). For the full sequence of events, and exactly when each callback (including the maps extraction progress) is invoked, see The map update process in detail.
Manual update or initial deployment
To deploy and update map data manually (from an SD card for example), you simply have to put/replace the map data in the path specified in the Core.init method (mapsDeploymentPath argument) before initializing the Core. Make sure that the deployed map package contains the mapsversion file, otherwise the licensing system will replace the map data that you have deployed.
If you deploy map data on an SD card and your app targets Android 11+, you need to deploy map data in a path that corresponds to the external scoped storage of your application. For example :
If your absolute path to the external scoped storage of your application is : /sdcard/Android/data/com.example.app/files (which also corresponds to : /storage/self/primary/Android/data/com.example.app/files)
You need to use this base path for deploying your map data because it corresponds to the external scoped storage of your application stored on the SD card : /storage/sdcard1/Android/data/com.benomad.basicplanner/files/
You can use the Device File Explorer of Android Studio to get the path to the external scoped storage of your application. As you can see in this example, the path /sdcard/Android/data/ (or /storage/self/primary/Android/) corresponds to the internal memory of the device where applications data is located, and /storage/sdcard1/Android/data/ corresponds to the SD card storage where applications data can be located too.
The OTA map update process in detail
The Observables (callbacks) section documents each listener on its own. This section explains how they fit together, in what order they fire, and what your application is responsible for, so you can implement map updates end to end.
How map data reaches the device
Map data reaches a device by download from the BeNomad servers, either as a first deployment or as a later update. When no maps are deployed yet, the Core downloads them over the network, and it also keeps deployed maps up to date by downloading newer versions when they become available.
The deployment mode (full maps or hybrid maps) is fixed by your purchase UUID, not chosen at runtime. You can query it after initialization with Core.getInstance().isUsingHybridMaps().
Two rules apply to every callback described below:
All callbacks are delivered on the main (UI) thread. You can update your UI directly from them, but you must not run long or blocking work inside a callback, because that would freeze the UI.
Register your observers before calling
Core.init(...). Some events fire early during initialization, so an observer added afterwards can miss them. Observers are kept with strong references and are never removed automatically, so call the matchingremoveOn...Observer(...)when your observer (for example an Activity or a Fragment) is destroyed, otherwise you leak it.
How OTA updates are applied (staged download, activation on next init)
An online update is never swapped underneath a running application. The new data is downloaded and prepared into a separate staging area, while the maps currently in use stay untouched. The staged update becomes active only the next time you call Core.init(...), which is usually your next application launch.
This design has three consequences you can rely on:
Your app keeps using the previously deployed maps until you re-initialize the Core.
An interrupted or failed update cannot corrupt the maps in use, because the swap only happens after a fully successful download.
Re-initializing the Core is what activates the new data, so you must trigger a re-init (or a restart) for the user to get the update.
There is one exception: the very first deployment, when the device has no maps yet, is applied immediately during the same init, because there is nothing in use to protect.
Online update lifecycle, step by step
This is the flow when maps are already deployed and a newer version exists on the BeNomad servers. Each arrow in the timeline below is a callback you can observe:
Core.init(...) ---> onCoreReady() app runs on the current maps
|
| (the Core checks for a newer version in the background)
v
onMapUpdateAvailable() a newer map version exists on the server
|
| YOUR decision, in response to the callback:
| downloadAvailableUpdate(true) accept and start the download
| downloadAvailableUpdate(false) decline
v
onMapDownloadProgress(progress, downloaded, total) repeated while the package downloads (sizes in KB)
v
onMapExtractionProgress(progress) repeated while the downloaded package is unpacked
v
onMapDownloaded() update fully downloaded and staged (NOT active yet)
|
| ... the app keeps running on the OLD maps ...
v
(next) Core.init(...) ---> onCoreReady() staged update is applied; app now runs on the NEW mapsAvailability. After initialization, the Core checks for a newer map version in the background, on a periodic schedule rather than continuously (by default, about once every 24 hours after the application started). Because the check is periodic,
onMapUpdateAvailable()can arrive at any time while your app runs, not only right afterinit. This is another reason to register the observer up front.Your decision. The download does not start on its own. In response to
onMapUpdateAvailable(), callCore.getInstance().downloadAvailableUpdate(true)to start it, ordownloadAvailableUpdate(false)to decline. Only call this method afteronMapUpdateAvailable()has fired.Download. While the package downloads, you receive
onMapDownloadProgress(progress, downloaded, total), whereprogressruns from 0 to 100 anddownloadedandtotalare sizes in kilobytes.Preparation. The downloaded package is unpacked and prepared, and
onMapExtractionProgress(progress)reports this step from 0 to 100.Staged.
onMapDownloaded()fires once the update is fully downloaded and staged. At this point your running app is still using the previous maps.Activation. On your next
Core.init(...), the staged update is swapped into place, andonCoreReady()is called with the new maps active.
onMapDownloadProgress is the same callback used for the initial online download described next, so a single progress handler serves both cases.
Initial online deployment vs updates
When no maps are deployed and none are bundled in assets, and the device is online, the Core downloads them automatically during init itself. This first deployment differs from an update in several ways:
You do not receive
onMapUpdateAvailable(), and you do not calldownloadAvailableUpdate(...).onMapDownloadProgress(...)andonMapExtractionProgress(...)fire duringinit, beforeonCoreReady().The maps are deployed immediately and are active as soon as
initfinishes.
Because of this, onMapDownloaded() means "the initial maps are downloaded and now active" in the first-deployment case, but "the update is downloaded and staged for the next launch" in the update case. There is no separate flag distinguishing the two: tell them apart by whether you had already reached onCoreReady() with working maps. Note that automatic online deployment is not supported on an SD card (see Manual update or initial deployment).
Simulating a map update for testing
During development you often need to exercise your map download handling without waiting for BeNomad to publish a newer map version. You can force the Core to download the maps again by deleting the mapsversion file from the deployed maps folder (the mapsDeploymentPath you passed to Core.init, which defaults to the Maps folder returned by Core.getDefaultMapsPath). The mapsversion file sits at the root of that folder.
Without it, the licensing system can no longer identify the version of the maps on the device, so on the next Core.init it treats them as maps to redeploy and downloads them again from the BeNomad servers. This is the same behavior warned about in Manual update or initial deployment, used here on purpose. The device must be online for the re-download to happen.
You then observe the same map download callbacks as a server download: onMapDownloadProgress(...), then onMapExtractionProgress(...), then onMapDownloaded(). This lets you validate your progress UI and completion handling end to end. Use this only during development: never delete the mapsversion file in a shipping application, because it forces a full re-download of the map data.
Error handling during the map update
Two distinct callbacks report problems, and the Core chooses between them based on the underlying error:
onMapDownloadError(error)receives map download and preparation failures, such as a network failure while downloading the package, not enough storage space, or an unpacking failure.onLicenseError(error)receives licensing failures.
The Core retries a failing download several times before surfacing an error. Because the folder swap only runs after a fully successful download, a failed or aborted update leaves your currently deployed maps intact and usable. When the licensing server is unreachable but a valid cached license exists, the Core falls back to the cached license and does not report an error (see The OnOfflinePeriodExpiredCallback to be notified when the offline period has expired).
Hybrid maps (on-demand map data)
If your purchase uses hybrid maps, map data ships partial and the rest is streamed on demand as the map is used. After onCoreReady(), Core.getInstance().isUsingHybridMaps() tells you whether hybrid mode is active. On-demand hybrid downloads are a separate channel from the whole-map update flow above:
Progress is reported through
onHybridMapDownloadProgress(status, progress), with its own status codes (-3 not enough storage, -2 internal error, -1 download failed, 0 started, 1 in progress, 2 finished), not throughonMapDownloadProgress.You can force a full download for a rectangular area with
loadFullMapsInArea(area, asyncMode), or for a country (ISO 3166 Alpha-3 code) withloadFullMapsInCountry(countryCode, asyncMode). Both return anError?that is null on success,CORE_NOT_YET_INITIALIZEDif the Core is not ready, orCORE_NOT_USING_HYBRID_MAPSif your purchase is not hybrid.asyncModedefaults to false, meaning the call blocks until the download finishes.cancelPendingHybridDownloads()cancels queued hybrid downloads.
Implementation checklist and full sample
Register every observer you need (at minimum
addOnMapUpdateAvailableObserver,addOnMapDownloadProgressObserver,addOnMapExtractionProgressObserver,addOnMapDownloadedObserver, andaddOnMapDownloadErrorObserver) before callingCore.init(...).Call
Core.init(...)once; a second concurrent call returns a BUSY error.In
onMapUpdateAvailable(), ask the user, then calldownloadAvailableUpdate(true)ordownloadAvailableUpdate(false).Drive your progress UI from
onMapDownloadProgress, then fromonMapExtractionProgress.On
onMapDownloaded(), tell the user the update will apply on the next launch; do not assume the new maps are live yet.Re-initialize the Core (typically at the next launch) to activate the staged update.
Handle
onMapDownloadErrorandonLicenseError.Remove your observers when their owner is destroyed.
import com.benomad.msdk.core.Core
import com.benomad.msdk.core.callbacks.OnMapUpdateAvailable.OnMapUpdateAvailableCallback
import com.benomad.msdk.core.callbacks.OnMapDownloadProgress.OnMapDownloadProgressCallback
import com.benomad.msdk.core.callbacks.OnMapExtractionProgress.OnMapExtractionProgressCallback
import com.benomad.msdk.core.callbacks.OnMapDownloaded.OnMapDownloadedCallback
import com.benomad.msdk.core.callbacks.OnMapDownloadError.OnMapDownloadErrorCallback
import com.benomad.msdk.errormanager.Error
class MapUpdateHandler(
private val core: Core = Core.getInstance()
) : OnMapUpdateAvailableCallback,
OnMapDownloadProgressCallback,
OnMapExtractionProgressCallback,
OnMapDownloadedCallback,
OnMapDownloadErrorCallback {
// Call this BEFORE core.init(...)
fun register() {
core.addOnMapUpdateAvailableObserver(this)
core.addOnMapDownloadProgressObserver(this)
core.addOnMapExtractionProgressObserver(this)
core.addOnMapDownloadedObserver(this)
core.addOnMapDownloadErrorObserver(this)
}
// Call this when your owner (Activity or Fragment) is destroyed
fun unregister() {
core.removeOnMapUpdateAvailableObserver(this)
core.removeOnMapDownloadProgressObserver(this)
core.removeOnMapExtractionProgressObserver(this)
core.removeOnMapDownloadedObserver(this)
core.removeOnMapDownloadErrorObserver(this)
}
// A newer map version is available on the server. Ask the user, then decide.
override fun onMapUpdateAvailable() {
val userAccepted = askUserToUpdate() // your own UI
core.downloadAvailableUpdate(userAccepted)
}
// All callbacks below are delivered on the main thread.
override fun onMapDownloadProgress(progress: Float, downloaded: Float, total: Float) {
// progress: 0..100, downloaded and total in KB
}
override fun onMapExtractionProgress(progress: Float) {
// progress: 0..100 (unpacking the downloaded package)
}
override fun onMapDownloaded() {
// The update is downloaded and staged. It becomes active on the next Core.init(...).
// Prompt the user to restart, or re-initialize the Core when appropriate.
}
override fun onMapDownloadError(error: Error) {
// Download or preparation failed. The maps currently in use are left intact.
}
private fun askUserToUpdate(): Boolean = true
}Observables (callbacks)
There are other observables that you can subscribe to before starting the Core initialization :
The OnInitProgressCallback to get the Core initialization progress
OnInitProgressCallback to get the Core initialization progressUsage :
Core.getInstance().addOnInitProgressObserver(this)
// the observer parameter in this example is the class that implements the OnInitProgressCallback, but it can also be an object variable
override fun onInitProgress(progress: Float) {
// progress is the current global progress of the Core initialization from 0 to 100
}The OnMapDownloadProgressCallback to get the progress of the maps download
OnMapDownloadProgressCallback to get the progress of the maps downloadUsage :
Core.getInstance().addOnMapDownloadProgressObserver(this)
override fun onMapDownloadProgress(progress: Float, downloaded: Float, total: Float) {
//progress is the current progress of the maps download from 0 to 100
//downloaded is the size in KB of the data already downloaded
//total is the total size in KB to download
}The OnHybridMapDownloadProgressCallback to get the progress of the hybrid maps download
OnHybridMapDownloadProgressCallback to get the progress of the hybrid maps downloadNote: This callback is useful only if you're using the hybrid mode for the maps data
Usage :
Core.getInstance().addOnHybridDownloadProgressObserver(this)
override fun onHybridMapDownloadProgress(status: Int, progress: Double) {
//status is the current status of the hybrid download
/**
-3: Not enough storage space,
-2: Internal error,
-1: Download failed (network error),
0: Download started,
1: Download in progress and the percentage is available,
2: Download finished
*/
//progress is the current progress of the maps download from 0 to 100
}The OnMapExtractionProgressCallback to get the progress of the maps extraction
OnMapExtractionProgressCallback to get the progress of the maps extractionUsage :
Core.getInstance().addOnMapExtractionProgressObserver(this)
override fun onMapExtractionProgress(progress: Float) {
//progress is the current progress of the map extraction from 0 to 100
}The OnLicenseErrorCallback to be notified about errors with the license
OnLicenseErrorCallback to be notified about errors with the licenseUsage :
Core.getInstance().addOnLicenseErrorObserver(this)
override fun onLicenseError(error: Error) {
val messageId = error.messageId // the message id of the error
val errorExplanation = error.detailedMessage // a more detailed message about the error (not always available, in this case an empty String is returned)
}The OnLicenseExpiredCallback to be notified when the license has expired
OnLicenseExpiredCallback to be notified when the license has expiredUsage :
Core.getInstance().addOnLicenseExpiredObserver(this)
override fun onLicenseExpired() {
//this is triggered when your license has expired
}The OnMapUpdateAvailableCallback to be notified when a map update is available
OnMapUpdateAvailableCallback to be notified when a map update is availableUsage :
Core.getInstance().addOnMapUpdateAvailableObserver(this)
override fun onMapUpdateAvailable() {
//a map update is available.
Core.getInstance().downloadAvailableUpdate(userChoice) //user choice is a Boolean that indicates if the user accepted the update download or refused it.
}Note : To be notified about the update download progress, use OnMapDownloadProgressCallback which is the same callback used for the maps download progress.
The OnMapDownloadedCallback to be notified when the map update is downloaded, or when the initial map data is downloaded
OnMapDownloadedCallback to be notified when the map update is downloaded, or when the initial map data is downloadedUsage :
Core.getInstance().addOnMapDownloadedObserver(this)
override fun onMapDownloaded() {
//the initial map download or the map update download has finished. In case of an update, it will be installed on next application start
}The OnMapDownloadErrorCallback to be notified when an error occurred during the map download, whether it's the original download or an update
OnMapDownloadErrorCallback to be notified when an error occurred during the map download, whether it's the original download or an updateUsage :
Core.getInstance().addOnMapDownloadErrorObserver(this)
override fun onMapDownloadError(error: Error) {
//called if something happened during the map download (connection error, no space available, unzipping, etc)
val messageId = error.messageId // the message id of the error
val errorExplanation = error.detailedMessage // a more detailed message about the error (not always available, in this case an empty String is returned)
}The OnOfflinePeriodExpiredCallback to be notified when the offline period has expired
OnOfflinePeriodExpiredCallback to be notified when the offline period has expiredThe Core needs to be initialized with an online connection at first. Then, during a determined "offline period", the Core can be initialized without any internet connection. When this offline period is expired, this callback onOfflinePeriodExpired is called. It means that the Core can't be initialized without an internet connection : it needs an internet connection to validate your license in the BeNomad licensing system. After the Core is initialized with an internet connection again, the offline period is reset : you can initialize the Core without an internet connection.
Usage :
Core.getInstance().addOnOfflinePeriodExpiredObserver(this)
override fun onOfflinePeriodExpired() {
//this is triggered when the offline period has expired
//you have to initialize the Core with an active internet connection, otherwise the Core won't be initialized anymore
}