Usage guidelines
This page outlines the recommended usage guidelines for the Maps SDK for Flutter. Adhering to these recommendations can enhance code reliability and help prevent issues that may arise from improper implementation.
Working with images
The images are represented in an abstract form which is not directly drawable on the UI. The responsible classes are Img
, AbstractGeometryImg
, LaneImg
, SignpostImg
, and RoadInfoImg
.
These classes provide the following methods and getters:
uid
: Gets the unique ID of the image. If two images are the same, then they have the same UID. This allows the user to optimize UI redraws and trigger an update only when the image changes.isValid
: Verifies if the image data is valid. If the image is not valid, thengetRenderableImageBytes
andgetRenderableImage
will returnnull
.getRenderableImageBytes
: Returns aUint8List
which can be displayed on the UI using theImage.memory
constructor. It returnsnull
if the image is invalid.getRenderableImage
: Returns aRenderableImg
which contains the image size and theUint8List
that can be drawn on the UI. It returnsnull
if the image is invalid.
Plain images
The Img
class corresponds to plain (usually non-vector) images. These images have a recommended size and aspect ratio but can be resized to any dimension in order to be drawn (with possible loss of quality).
The user can instantiate an Img
class:
- Using the constructor which takes the
Uint8List
image data and format. - Using the async
fromAsset
factory method which takes the asset key (and optionally a custom bundle).
Special images
The AbstractGeometryImg
, LaneImg
, SignpostImg
, and RoadInfoImg
classes correspond to vector images generated by the SDK based on internal data. They provide customizable options for rendering. They do not have a default size or aspect ratio.
The LaneImg
, SignpostImg
, and RoadInfoImg
classes might be best displayed at a particular aspect ratio depending on the image content. To render the images at a suitable size chosen by the SDK based on the user-provided size, set the allowResize
parameter of the getRenderableImage
/getRenderableImageBytes
methods to true
.
If the allowResize
parameter is set to false
, then rendering will always be done at the size specified by the user (the resulting image might be distorted).
Use the getRenderableImage
method instead of getRenderableImageBytes
when setting the allowResize
parameter to true
. This way, the size of the rendered image is also provided within the resulting RenderableImg
class.
The particularities of each image type are presented in the table below:
Class | Size and aspect ratio | Customizable render options | The size of the renderable image returned by the getRenderableImage /getRenderableImageBytes methods | Instantiable by the user |
---|---|---|---|---|
Img | Usually fixed and retrievable via the size and aspectRatio getters. | Not available | Will always render at the size specified by the user if provided, or the recommended size for the particular image otherwise. | Yes, via the provided constructor and the fromAsset static method. It can be also provided by the SDK. |
AbstractGeometryImg | Generated by the SDK. Size and aspect ratio not retrievable. | Yes, via AbstractGeometryImageRenderSettings | Will always render at the size specified by the user if provided, or the SDK default image size otherwise. | No. Can only be provided by the SDK. |
LaneImg | Generated by the SDK. Size and aspect ratio not retrievable. | Yes, via LaneImageRenderSettings | Will render at the size specified by the user if the allowResize parameter is false. If allowResize is true, the SDK will render at an aspect ratio suitable for the particular image based on the size provided by the user. | No. Can only be provided by the SDK. |
SignpostImg | Generated by the SDK. Size and aspect ratio not retrievable. | Yes, via SignpostImageRenderSettings | Will render at the size specified by the user if the allowResize parameter is false. If allowResize is true, the SDK will render at an aspect ratio suitable for the particular image based on the size provided by the user. | No. Can only be provided by the SDK. |
RoadInfoImg | Generated by the SDK. Size and aspect ratio not retrievable. | Background color customizable via Color | Will render at the size specified by the user if the allowResize parameter is false. If allowResize is true, the SDK will render at an aspect ratio suitable for the particular image based on the size provided by the user. | No. Can only be provided by the SDK. |
For debugging purposes, the images in Uint8List
format can be encoded as Base64 strings using base64Encode
and viewed on a Base64 image decoding website. This approach allows for easy inspection of image data during development.
Approaches
The different approaches regarding getting the images in a Uint8list
form from the SDK are the following, ilustrated for the lane image contained within NavigationInstruction
. The general principles are the same for all types of images.
Approach 1
Uint8List? imageData = instruction.getLaneImage(
size: const Size(200, 100),
format: ImageFileFormat.png,
);
This approach is simple and straightforward, with minimal boilerplate code. However, it comes with a few limitations:
- Allowing the engine to provide the image using a suitable size/aspect ratio is not available (for now). All images will be returned at the size explicitly requested by the user.
- There is no way to retrieve the recommended image size based on the content.
- The image UID is not accessible to the user.
The getLaneImage
method returns null if the image is invalid.
Approach 2
LaneImg laneImage = instruction.laneImg;
Uint8List? imageData = newImage!.getRenderableImageBytes(size: const Size(200, 100), format: ImageFileFormat.png, allowResize : true);
// Get more data about the lane image
int uid = laneImage.uid;
This approach allows the user to configure the allowResize
parameter but does not provide metadata about the provided Uint8List
image:
- When
allowResize
is set to true, the returnedUint8List
image may be resized to a more suitable size, which could differ from the requested dimensions. However, the actual size of the returned image is not exposed to the user. - When
allowResize
is set to false, the image will be returned at the exact size requested, though it may have an improper aspect ratio.
The user is able to efficiently use the image UID in order to optimize redraws.
The getRenderableImageBytes
method returns null if the image is invalid.
Approach 3
LaneImg laneImage = instruction.laneImg;
RenderableImg? renderableImage = newImage.getRenderableImage(size: const Size(200, 100), format: ImageFileFormat.png, allowResize : true);
Uint8List? imageData = renderableImage?.bytes;
// Get more data about the lane image
int uid = laneImage.uid;
// Get the actual size of the image
int? width = renderableImage?.width;
int? height = renderableImage?.height;
This approach provides greater flexibility by allowing the user to configure the allowResize
parameter and allows the user to get metadata about the provided Uint8List
image:
- When
allowResize
is set to true, the returnedUint8List
image may be resized to a more suitable size, which could differ from the one originally requested. - The actual dimensions of the returned image can be accessed via the getters in the
RenderableImg
object. - When
allowResize
is set to false, the image will be returned at the exact size requested, though it may have an unsuitable aspect ratio.
The user can leverage the image UID to optimize redraw operations efficiently.
The getRenderableImage
method returns null if the image is invalid.
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 Flutter classes. Use the callback methods provided by the SDK for seamless integration.
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.
Avoid using members annotated with @internal.
Members, methods, and fields annotated with @internal are intended for internal use and should not be accessed or used by API users.
Examples of Internal Members and Classes to Avoid:
-
Fields
- Avoid using the
pointerId
,mapId
fields/getters from classes.
- Avoid using the
-
Methods
- Constructors that initialize
pointerId
ormapId
with -1. - Do not use
fromJson
andtoJson
methods, as the structure of the generated JSON may change across SDK versions. - Avoid invoking listener methods such as
notify...
orhandleEvent
. - Avoid using the
init
methods from classes.
- Constructors that initialize
-
FFI-Related Classes
- Refrain from interacting with classes related to the foreign function interface (FFI), such as
GemKitPlatform
,GemSdkNative
,GemAutoreleaseObject
.
- Refrain from interacting with classes related to the foreign function interface (FFI), such as
Using these internal elements can lead to unexpected behavior and compatibility issues since they are not part of the public API contract and may change or be removed without notice.
Check the error code
Many methods return error codes that indicate the outcome of the operation. Ensure that all failure cases are handled appropriately to maintain code robustness and prevent unexpected behavior.
The GemError
enum defines the possible values corresponding to the outcome of an operation. The following values indicate successful outcomes:
success
: The operation completed successfully.reducedResult
: The operation completed successfully, but only a subset of the results was returned.scheduled
: The operation has been scheduled for execution at a later time.
Other SDK methods might result in failure in certain cases (if the system runs out of memory for example), even if they don't return a GemError
value. In this cases, the error code can be obtained via the ApiErrorService
class. After each operation the error code can be consulted in the following way:
// Any SDK call...
GemError error = ApiErrorService.apiError;
if (error == GemError.success) print("The last operation succeeded.");
else print("The last operation failed with error: $error");
Make sure to check the error code using the ApiErrorService.apiError
getter immediately after the operation completes. Delaying this check could result in other SDK operations being executed, which might overwrite the error code.
Get notified about operation error code changes
The ApiErrorService
class also provides a way to get notified when a SDK operation error code gets updated. The following snippet will register a listener which will be called each time the ApiErrorService.apiError
value updates.
ApiErrorService.registerOnErrorUpdate((error) {
if (error == GemError.success) print("An operation succeeded");
else print("An operation failed with error code $error")
});
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.
Avoid SDK operations inside isolates
The SDK does not support executing provided methods within isolates. The SDK already leverages multithreading internally where applicable. Doing SDK operations inside isolates may lead to exceptions.
Return the result from a callback inside a method
Most time-consuming operations (such as search, route calculation, etc.) use callbacks to return results. To integrate these results into an API user-defined function, a Completer
can be used to convert the callback into a Future
:
Future<ResultType?> myFunction() async{
final Completer<ResultType?> completer = Completer<ResultType?>();
SomeGemService.doSomething(
onComplete: (error, result){
if (error == GemError.success && result != null){
completer.complete(result);
}
}
)
return completer.future;
}
This approach simplifies user code by converting callback parameters into a Future
return type, making asynchronous handling easier and more readable. For more details about the Completer
class, refer to the Dart documentation.
Provide SDK logs when reporting issues
When reporting issues, please include the SDK logs to help us diagnose and resolve problems more efficiently.
To configure logging and print messages to the console, add the following code:
Debug.logCreateObject = true;
Debug.logCallObjectMethod = true;
Debug.logListenerMethod = true;
Debug.logLevel = GemLoggingLevel.all;
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.
- Platform details (e.g., iOS/Android, OS version, device model).
- Console logs printed during the issue.
- 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.