Best practices
In the following sections, we will guide you through the most common usage scenarios and reveal tips and easy-to-understand guidelines to help you get the most out of the HERE SDK for Flutter.
Optimization strategies
Explore further options to enhance the performance of the HERE SDK:
- Optimize map caching: Adjust the caching mechanisms by following the guidelines in our map caching documentation.
- Conditional HERE SDK initialization: To conserve resources, initialize the HERE SDK only when it is necessary.
- Adjust the frame fate: Modify the frame rate settings of the
MapViewto balance performance and visual fluidity. Detailed guidance can be found in the frame rate adjustment section below. - Simplify visuals with custom map styles: Employ custom map styles that include fewer elements to render, thereby enhancing rendering efficiency and reducing computational load.
- Preload map data: (Only available for Navigate) Use offline maps to download and store map data in advance, allowing for smoother access and reduced reliance on live data connections.
Additionally, the HERE SDK provides versatile configuration options across various engines. For instance, with the SearchEngine, you can define SearchOptions to refine and limit the scope of returned search results.
Handle low memory states
When your application runs on a memory-constrained device or under heavy load, it is important to manage memory efficiently to avoid crashes. The HERE SDK provides mechanisms to optimize its memory usage under such scenarios.
Detect and respond to low memory warnings
To respond to memory pressure from the operating system (e.g., via WidgetsBindingObserver in Flutter), implement a handler that listens to memory warnings and takes action accordingly. Below is a possible implementation:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didHaveMemoryPressure() {
super.didHaveMemoryPressure();
handleLowMemory();
}A possible implementation for handleLowMemory() is shown below.
Enable low memory mode and purge the cache
To proactively reduce memory consumption, enable lowMemoryMode in SDKOptions and optionally purge the cache:
handleLowMemory() async {
print("System is running extremely low on memory!");
print("Clearing HERE SDK's internal memory caches.");
SDKNativeEngine.sharedInstance!.purgeMemoryCaches(SDKNativeEnginePurgeMemoryStrategy.full);
AuthenticationMode authenticationMode = AuthenticationMode.withKeySecret("YOUR_ACCESS_KEY_ID", "YOUR_ACCESS_KEY_SECRET");
SDKOptions sdkOptions = SDKOptions.withAuthenticationMode(authenticationMode);
sdkOptions.lowMemoryMode = true;
try {
await SDKNativeEngine.makeSharedInstance(sdkOptions);
print("Low Memory: Low memory mode has been enabled.");
} on InstantiationException catch (e) {
print("Low Memory: Failed to enable low memory mode: ${e.toString()}");
}
} Setting lowMemoryMode = true configures the HERE SDK’s internal caches to use less memory. This can be beneficial on devices with limited RAM or during times of high memory pressure.
Turning on the lowMemoryMode will impact the performance of several HERE SDK features such as rendering the map view. Therefore, it may make sense to enable it only after you receive a low-memory warning from the operation system. The next section describes how to do this.
When the system sends memory pressure signals, use the following method to optionally clear the HERE SDK’s internal caches:
SDKNativeEngine.sharedInstance!.purgeMemoryCaches(SDKNativeEnginePurgeMemoryStrategy.full);This will release memory held by all in-memory caches and will help prevent OOM (Out of Memory) crashes.
Currently, only SDKNativeEnginePurgeMemoryStrategy.full is supported, which clears all cached content without preserving any data.
Free resources
All HERE SDK classes will be garbage collected by Flutter if the instance is no longer referenced or set to null.
When the hosting widget's lifetime has ended, you can free resources by calling SDKNativeEngine.sharedInstance?.dispose(). Also, all references to SDKNativeEngine must be set to null (if any). Calling dispose() will stop pending requests and close open files and databases that are still running. After calling dispose() any related HERE SDK feature should no longer be used - unless you initialize the HERE SDK again: if you have created engines like SearchEngine() or RoutingEngine() using the default constructor, then these instances need to be recreated as well. Basically, all engines that used the same instance of the SDKNativeEngine need to be recreated after it was disposed.
Remove unused voice and font files to reduce the package size
By removing unused voice files like voice_package_ar-SA you can reduce the package size of the HERE SDK. These voice packages are only needed when you want to use turn-by-turn navigation with text-to-speech engines. Note that voice files are only included as part of Navigate.
On top, the HERE SDK contains several font files to render map labels for all supported languages. If you want to optimize the size of the overall app, you can remove selected fonts.
For example, the DroidSansFallback font uses the Simplified Chinese ideographs for shared Unicode code points.
On iOS, to remove this font, open the HERE SDK plugin folder and remove the following files: ios/Frameworks/heresdk.xcframework/ios-arm64/heresdk.framework/geoviz/fonts/DroidSansFallback.ttf.
For Android, please follow the steps below.
The DroidSansFallback is packaged in the HERE SDK AAR file (which can be opened by appending ".zip" to the file name):
assets/geoviz/fonts/DroidSansFallback.ttf
As another example, if you do not need to render the full set of Japanese characters, you can remove:
assets/geoviz/fonts/NotoSansJP-Regular.otf
Note that such fonts may not only be used in Japan and China, but also for other areas around the world when users switch their device language to Japanese or Chinese.
In order to remove files, you need to edit the app's build.gradle file.
For Android Gradle plugin versions >= 7.0 use AndroidResources like shown below:
android {
...
androidResources {
def languagesToRemove = ['ru-Ru', 'zh-HK']
def languagePatterns = languagesToRemove.collect {"!voice_package_${it}*"}.join(":")
ignoreAssetsPattern "${languagePatterns}:!DroidSansFallback.ttf:!NotoSansJP-Regular.otf"
}
...
}This excludes the listed fonts and voice assets from the final package. If you need to exclude more assets you need to list them as well.
Note that this example uses the delimiter syntax to exclude files.
Here you can find a list of all supported voice languages together with the name of the related voice skin that is stored inside the HERE SDK framework. Alternatively, look into the AAR by unzipping it and search for the voice and/or font files that you want to exclude.
If your Android Gradle plugin version is below 7.0 you can replace androidResources with aaptOptions.
NoteWhen you remove a font, it is recommended to switch the map language to any other language than the removed font. At least one font needs to be left to see any map labels. Note that each time a label needs to be displayed in a language for a font that was removed an error message is logged - for each character that is missing.
Note that the size of a font file may range from a few hundred kilobytes to a few megabytes in size. After removing selected fonts your application consumes less space when it is installed on a device. The maximum amount that you can save with these steps is approximately 11 MB.
Remove unused truck restriction icons to reduce the package size
You can manually remove unused truck restriction icons from the following paths inside the HERE SDK binary:
- /assets/geoviz/assets/oslo/truck_restriction/night/ui/
- /assets/geoviz/assets/oslo/truck_restriction/day/ui/
These icons are only used when creating vehicle restriction icons with IconProvider.createVehicleRestrictionIcon() and the IconProviderAssetType.ui setting. They are better suited for UI rendering compared to their IconProviderAssetType.map counterparts. If these files are removed, the HERE SDK will automatically fall back to using the map assets.
These SVG icons are included in the HERE SDK binary and occupy approximately 264 KB when compressed.
If you do not plan to use the IconProvider and if you do not need to show the MapFeatures.vehicleRestriction layer, then you can save considerably more space by removing the entire folder assets/geoviz/assets/oslo/truck_restriction.
To remove these files, follow the steps outlined in the section above.
Use ABI splits for Android to remove unused ABIs
Size management: with ABI splits you can reduce the package size of the HERE SDK AAR binary file. As a result, your application will occupy less storage space on a device.
By default, the HERE SDK includes the following ABIs for Android: armeabi-v7a, arm64-v8a (mainly used for devices) and x86, x86_64 (mainly used for emulators). You can enable ABI splits to build your app, for example, only for the armeabi-v7a or arm64-v8a architectures.
Do this by modifying your app's build.gradle file:
android {
(...)
splits {
abi {
enable true
reset()
include 'x86_64', 'arm64-v8a' // Choose what you need.
universalApk false
}
}
(...)
}
Now, when you execute ./gradlew assembleRelease from command line, the following two APKs are generated: app-x86_64-release.apk and app-arm64-v8a-release.apk. Each APK contains only the desired ABI and is therefore much smaller in size. If you change the splits block to set universalApk true, then also a universal APK is generated that contains all ABIs, which is obviously much bigger.
For more information about the splits Gradle block, see Configure Multiple APKs for ABIs.
NoteBy default, an APK built with the HERE SDK for release will be around 103 MB or higher - depending on the app features. Therefore, in order to release an app the Play Store, it is required to use either ABI Splits or Android App Bundles (AAB): as of now, the Play Store limits APKs to 100 MB and for ABBs the limit is 150 MB. If you do not want to maintain several APKs built via ABI splits for selected architectures, consider to use ABBs instead.
Map rendering modes
The HereMap on Android offers the flexibility of using either Hybrid Composition or Virtual Display mode, with the former recommended for Android 12 or higher but potentially impacting performance on older versions, and the latter typically paired with TextureView for optimal rendering.
More information about Hybrid Composition and Virtual Display modes can be found here.
When opting for Hybrid Composition, you can choose between available renderMode options based on your requirements, as Flutter suggests it to avoid post-orientation graphical glitches.
Additionally, the MapView on Android provides MapRenderMode.surface for optimal performance, albeit with potential graphical glitches on Android 12 and 13, while MapRenderMode.texture offers smoother rendering for complex UI or multiple map instances, though at the cost of increased performance overhead. This choice is made via HereMapOptions.renderMode, internally setting either SurfaceView or TextureView for rendering, noting that TextureView should be used with Virtual Display mode.
On iOS devices, however, these modes have no effect.
Adjust the frame rate
By default, the MapView is rendered with 60 frames per second (FPS). Via mapView.frameRate the maximum frame rate can be adjusted - for example, to reduce CPU / GPU usage on low end devices. It is also possible to deactivate automatic render cycles by setting FPS to 0. Setting negative values has no effect. The value can be set individually per MapView instance - in case your app contains multiple MapView's.
Another option is to use custom map styles that contain less elements to render.
Protect your credentials
Your credentials should be protected to provide misuse by other parties that are not intended to use them.
One option to keep credentials secure is to store them on a secure server and retrieve them by requests using SSL connections.
For best practice, consider:
- To avoid keeping sensitive data in plain text.
- To transfer credentials using a secure communication channel.
- To store credentials using device security mechanisms and strong encryption ciphers.
- To add anti-tampering and anti-debugging code, so that a potential attacker cannot intercept data during dynamic runtime execution.
- Track the application usage to detect anomalies.
Callbacks and Listeners
- The HERE SDK exposes callbacks for single event notification such as for search results.
- For reoccurring event notifications such as for gesture events, listeners are used. When multiple listeners can be set, then the method pattern
add_x()andremove_x()is used as naming convention. If only one listener can be set at a time, theset_x()pattern is used that can be set tonullto stop listening. - It is the responsibility of the developer to handle errors inside the scope of a callback gracefully: As a guideline, code that can throw an exception should be handled.
Use TaskHandles to Cancel Asynchronous Operations
Most asynchronous methods provide a TaskHandle as immediate return value, while the final result of the operation will be returned in a completion handler with a delay. The TaskHandle provides status information and it allows to abort an ongoing operation.
Get access tokens for use with external REST APIs
Each time the HERE SDK is initialized, a new access token is generated internally. In case of multiple SDKNativeEngine instances, each instance holds its own token. You can also refresh and fetch the token via Authentication.authenticate(SDKNativeEngine.sharedInstance, callback) where the callback provides the token via authenticationData.token - you can use this token to access external HERE REST APIs.
For using the HERE SDK itself, you do not need to know the token - it is only required under the hood and the HERE SDK is handling the token generation automatically.
To protect our backends against misusage, a rate limit may apply when too many tokens are generated or when too many services are accessed in a too short time frame. In such a case, an engine will respond with a requestLimitReached error or similar. If you expect very high app usage, please talk to your HERE representative upfront to adapt the limits where needed.
Prepare your app for distribution on iOS
The included HERE SDK iOS framework (heresdk.xcframework) is not a fat binary, but it contains set of frameworks for several architectures - built for device (arm64) and simulator (x86_64). Therefore it contains multiple architectures. It consists of:
- A framework with architecture for device, this framework is used on end user’s device.
- A framework with two architectures for simulator, this framework is usually necessary only during development.
- Debug symbols for the device, which are uploaded automatically to Apple's crash analytics server.
This allows easy deployment on a simulator and on a real device. The debug symbols (dSYM) are used to symbolicate crashes in Xcode. Find more details on how to use the dSYM files to symbolicate crashes here.
NoteThe file size is optimized by Xcode at deployment time, with unnecessary frameworks being stripped out. Although you can manually remove files such as the dSYM debug symbols, this is not recommended. These files are relatively large, occupying around 80% of the size of the XCFW. However, while they are not necessarily needed during development, the debug symbols are crucial to get symbolized crash stacks for release builds. Note that symbolized crash stacks are treated with higher priority.
More information on symbolication and logs can be found here.
Since the HERE SDK framework conforms to Apple's XCFramework bundle type (XCFW), it is ready to be used for the distribution of your app on iOS devices via Apple's App Store.
As it is the practice, in Xcode you have to select a development team to sign your app, select a Generic iOS Device and select Product -> Archive.
The binary size of an exported app (that is resulting IPA) for a target device will occupy less space. Note that you can also manually remove files that are not required. However, please keep in mind that the IPA still contains all architectures - unless you explicitly excluded them when archiving: the actual size on the device will occupy much less space and Apple will ensure that only the required architectures are included. Thus, also the actual download size from the App Store will be much smaller. See also this Xcode guide. Therefore, we recommend to keep the HERE SDK framework as it is when the app is deployed through the App Store - since all size optimization happens automatically as part of the deployment process.
Privacy manifest files
As of May 2024, every release of an iOS application to the App Store requires the declaration of a privacy manifest in form of a PrivacyInfo.xcprivacy file. By default, this file is included in the heresdk.xcframework (XCFW) at the following paths:
- heresdk.xcframework/ios-arm64/heresdk.framework/PrivacyInfo.xcprivacy
- heresdk.xcframework/ios-arm64_x86_64-simulator/heresdk.framework/PrivacyInfo.xcprivacy
For Flutter plugins, find the XCFW here: here_sdk-xxx-4.xxx/ios/Frameworks/heresdk.xcframework
Publish an update for older versions (Explore)
If you need to publish an update for older versions prior to HERE SDK 4.18.2.0 please insert the privacy manifest file manually into the XCFW and use the below content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false />
<key>NSPrivacyTrackingDomains</key>
<array />
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>0A2A.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
</array>
</dict>
</plist>
Publish an update for older versions (Navigate)
If you need to publish an update for older versions prior to HERE SDK 4.18.2.0 please insert the privacy manifest file manually into the XCFW and use the below content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false />
<key>NSPrivacyTrackingDomains</key>
<array />
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
<string>0A2A.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeCoarseLocation</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false />
<key>NSPrivacyCollectedDataTypeTracking</key>
<false />
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false />
<key>NSPrivacyCollectedDataTypeTracking</key>
<false />
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
</dict>
</plist>Updated yesterday