Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ ServiceDefinitions.json
xcuserdata/
.swiftpm/
.last_build_id
.build/

# Android
local.properties
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This repository contains a Flutter plugin that provides a [Google Navigation](ht

| | Android | iOS |
| ------------------------------- | ------------- | --------- |
| **Minimum mobile OS supported** | API level 23+ | iOS 16.0+ |
| **Minimum mobile OS supported** | API level 24+ | iOS 16.0+ |

* A Flutter project
* A Google Cloud project
Expand Down Expand Up @@ -43,7 +43,7 @@ Set the `minSdk` in `android/app/build.gradle`:
```groovy
android {
defaultConfig {
minSdk 23
minSdk 24
}
}
```
Expand Down Expand Up @@ -199,6 +199,32 @@ This parameter has only an effect on Android.

```

#### Using Map IDs
You can configure your map by providing a `mapId` parameter during map initialization. Map IDs are created in the [Google Cloud Console](https://console.cloud.google.com/google/maps-apis/studio/maps) and allow you to [enable various Google Maps Platform features](https://developers.google.com/maps/documentation/android-sdk/map-ids/mapid-over#features-available), such as cloud-based map styling.

> [!NOTE]
> The `mapId` can only be set once during map initialization and cannot be changed afterwards. Both `GoogleMapsMapView` and `GoogleMapsNavigationView` support the `mapId` parameter.

For `GoogleMapsMapView`:

```dart
GoogleMapsMapView(
mapId: 'YOUR_MAP_ID', // Can only be set during initialization
...
)
```

For `GoogleMapsNavigationView`:

```dart
GoogleMapsNavigationView(
mapId: 'YOUR_MAP_ID', // Can only be set during initialization
...
)
```

For more information about map IDs and how to create them, see the [Google Maps Platform documentation](https://developers.google.com/maps/documentation/get-map-id).

See the [example](./example) directory for a complete navigation sample app.

### Requesting and handling permissions
Expand Down
8 changes: 4 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ android {
}

defaultConfig {
minSdk = 23
minSdk = 24
consumerProguardFiles 'proguard.txt'
}

dependencies {
implementation 'androidx.car.app:app:1.4.0'
implementation 'androidx.car.app:app-projected:1.4.0'
implementation 'com.google.android.libraries.navigation:navigation:6.2.2'
implementation 'androidx.car.app:app:1.7.0'
implementation 'androidx.car.app:app-projected:1.7.0'
implementation 'com.google.android.libraries.navigation:navigation:7.2.0'
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'io.mockk:mockk:1.13.8'
testImplementation 'junit:junit:4.13.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ object Convert {
options.minZoomPreference?.let { googleMapOptions.minZoomPreference(it.toFloat()) }
options.maxZoomPreference?.let { googleMapOptions.maxZoomPreference(it.toFloat()) }
googleMapOptions.zoomControlsEnabled(options.zoomControlsEnabled)
options.mapId?.let { googleMapOptions.mapId(it) }

return MapOptions(googleMapOptions, options.padding)
}
Expand Down Expand Up @@ -310,8 +311,8 @@ object Convert {
*/
fun convertWaypointToDto(waypoint: Waypoint): NavigationWaypointDto {
return NavigationWaypointDto(
waypoint.title,
convertLatLngToDto(waypoint.position),
waypoint.title ?: "",
waypoint.position?.let { convertLatLngToDto(it) },
waypoint.placeId,
waypoint.preferSameSideOfRoad,
waypoint.preferredHeading.takeIf { it != -1 }?.toLong(),
Expand Down Expand Up @@ -362,6 +363,7 @@ object Convert {
*/
fun convertDisplayOptionsFromDto(displayOptions: NavigationDisplayOptionsDto): DisplayOptions {
return DisplayOptions().apply {
// Only set if explicitly provided, otherwise SDK defaults are used.
if (displayOptions.showDestinationMarkers != null) {
this.hideDestinationMarkers(!displayOptions.showDestinationMarkers)
}
Expand Down Expand Up @@ -474,6 +476,7 @@ object Convert {
Navigator.RouteStatus.OK -> RouteStatusDto.STATUS_OK
Navigator.RouteStatus.QUOTA_CHECK_FAILED -> RouteStatusDto.QUOTA_CHECK_FAILED
Navigator.RouteStatus.WAYPOINT_ERROR -> RouteStatusDto.WAYPOINT_ERROR
Navigator.RouteStatus.DUPLICATE_WAYPOINTS_ERROR -> RouteStatusDto.DUPLICATE_WAYPOINTS_ERROR
}
}

Expand Down Expand Up @@ -802,17 +805,17 @@ object Convert {

private fun convertNavInfoStepInfo(stepInfo: StepInfo): StepInfoDto {
return StepInfoDto(
distanceFromPrevStepMeters = stepInfo.distanceFromPrevStepMeters.toLong(),
timeFromPrevStepSeconds = stepInfo.timeFromPrevStepSeconds.toLong(),
distanceFromPrevStepMeters = stepInfo.distanceFromPrevStepMeters?.toLong() ?: 0L,
timeFromPrevStepSeconds = stepInfo.timeFromPrevStepSeconds?.toLong() ?: 0L,
drivingSide = convertDrivingSide(stepInfo.drivingSide),
exitNumber = stepInfo.exitNumber,
fullInstructions = stepInfo.fullInstructionText,
fullRoadName = stepInfo.fullRoadName,
simpleRoadName = stepInfo.simpleRoadName,
roundaboutTurnNumber = stepInfo.roundaboutTurnNumber.toLong(),
stepNumber = stepInfo.stepNumber.toLong(),
fullInstructions = stepInfo.fullInstructionText ?: "",
fullRoadName = stepInfo.fullRoadName ?: "",
simpleRoadName = stepInfo.simpleRoadName ?: "",
roundaboutTurnNumber = stepInfo.roundaboutTurnNumber?.toLong() ?: 0L,
stepNumber = stepInfo.stepNumber?.toLong() ?: 0L,
lanes =
stepInfo.lanes.map { lane ->
stepInfo.lanes?.map { lane ->
LaneDto(
laneDirections =
lane.laneDirections().map { laneDirection ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,13 +530,23 @@ abstract class GoogleMapsBaseMapView(
return getMap().isTrafficEnabled
}

fun isBuildingsEnabled(): Boolean {
return getMap().isBuildingsEnabled
}

fun setBuildingsEnabled(enabled: Boolean) {
getMap().isBuildingsEnabled = enabled
}

fun getMyLocation(): Location? {
// Remove this functionality and either guide users to use separate flutter
// library for geolocation or implement separate method under
// [GoogleMapsNavigationSessionManager] to fetch the location
// using the [FusedLocationProviderApi].
@Suppress("DEPRECATION")
return getMap().myLocation
@Suppress("DEPRECATION") val location = getMap().myLocation
// Return null explicitly if location is not available to avoid NullPointerException
// when the platform channel tries to serialize the Location object
return if (location != null && location.provider != null) location else null
}

fun getCameraPosition(): CameraPosition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.mapsplatform.turnbyturn.model.NavInfo
import com.google.android.libraries.navigation.CustomRoutesOptions
import com.google.android.libraries.navigation.DisplayOptions
import com.google.android.libraries.navigation.GpsAvailabilityChangeEvent
import com.google.android.libraries.navigation.NavigationApi
import com.google.android.libraries.navigation.NavigationApi.NavigatorListener
import com.google.android.libraries.navigation.Navigator
Expand Down Expand Up @@ -342,10 +343,17 @@ constructor(
remainingTimeOrDistanceChangedListener =
Navigator.RemainingTimeOrDistanceChangedListener {
val timeAndDistance = getNavigator().currentTimeAndDistance
navigationSessionEventApi.onRemainingTimeOrDistanceChanged(
timeAndDistance.seconds.toDouble(),
timeAndDistance.meters.toDouble(),
) {}
// Only send event if we have valid time and distance data
if (
timeAndDistance != null &&
timeAndDistance.seconds != null &&
timeAndDistance.meters != null
) {
navigationSessionEventApi.onRemainingTimeOrDistanceChanged(
timeAndDistance.seconds.toDouble(),
timeAndDistance.meters.toDouble(),
) {}
}
}
}

Expand Down Expand Up @@ -495,7 +503,7 @@ constructor(
* @return [TimeAndDistance] object.
*/
fun getCurrentTimeAndDistance(): TimeAndDistance {
return getNavigator().currentTimeAndDistance
return getNavigator().currentTimeAndDistance!!
}

/**
Expand Down Expand Up @@ -749,6 +757,17 @@ constructor(
override fun onGpsAvailabilityUpdate(isGpsAvailable: Boolean) {
navigationSessionEventApi.onGpsAvailabilityUpdate(isGpsAvailable) {}
}

override fun onGpsAvailabilityChange(event: GpsAvailabilityChangeEvent?) {
if (event != null) {
navigationSessionEventApi.onGpsAvailabilityChange(
GpsAvailabilityChangeEventDto(
isGpsLost = event.isGpsLost,
isGpsValidForNavigation = event.isGpsValidForNavigation,
)
) {}
}
}
}
getRoadSnappedLocationProvider()?.addLocationListener(roadSnappedLocationListener)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.view.View
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.libraries.navigation.NavigationView
import com.google.android.libraries.navigation.OnNavigationUiChangedListener
import com.google.android.libraries.navigation.PromptVisibilityChangedListener
import io.flutter.plugin.platform.PlatformView

class GoogleMapsNavigationView
Expand Down Expand Up @@ -51,6 +52,8 @@ internal constructor(
null
private var _onNavigationUIEnabledChanged: OnNavigationUiChangedListener? = null

private var _onPromptVisibilityChanged: PromptVisibilityChangedListener? = null

override fun getView(): View {
return _navigationView
}
Expand Down Expand Up @@ -110,6 +113,10 @@ internal constructor(
_navigationView.removeOnNavigationUiChangedListener(_onNavigationUIEnabledChanged)
_onNavigationUIEnabledChanged = null
}
if (_onPromptVisibilityChanged != null) {
_navigationView.removePromptVisibilityChangedListener(_onPromptVisibilityChanged)
_onPromptVisibilityChanged = null
}

// When view is disposed, all of these lifecycle functions must be
// called to properly dispose navigation view and prevent leaks.
Expand Down Expand Up @@ -171,6 +178,11 @@ internal constructor(
}
_navigationView.addOnNavigationUiChangedListener(_onNavigationUIEnabledChanged)

_onPromptVisibilityChanged = PromptVisibilityChangedListener { promptVisible ->
viewEventApi?.onPromptVisibilityChanged(getViewId().toLong(), promptVisible) {}
}
_navigationView.addPromptVisibilityChangedListener(_onPromptVisibilityChanged)

super.initListeners()
}

Expand Down Expand Up @@ -246,6 +258,14 @@ internal constructor(
_isReportIncidentButtonEnabled = enabled
}

fun isIncidentReportingAvailable(): Boolean {
return _navigationView.isIncidentReportingAvailable()
}

fun showReportIncidentsPanel() {
_navigationView.showReportIncidentsPanel()
}

fun isTrafficPromptsEnabled(): Boolean {
return _isTrafficPromptsEnabled
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,22 @@ class GoogleMapsViewMessageHandler(private val viewRegistry: GoogleMapsViewRegis
getNavigationView(viewId.toInt()).setReportIncidentButtonEnabled(enabled)
}

override fun isIncidentReportingAvailable(viewId: Long): Boolean {
return getNavigationView(viewId.toInt()).isIncidentReportingAvailable()
}

override fun showReportIncidentsPanel(viewId: Long) {
getNavigationView(viewId.toInt()).showReportIncidentsPanel()
}

override fun isBuildingsEnabled(viewId: Long): Boolean {
return getView(viewId.toInt()).isBuildingsEnabled()
}

override fun setBuildingsEnabled(viewId: Long, enabled: Boolean) {
getView(viewId.toInt()).setBuildingsEnabled(enabled)
}

override fun isTrafficPromptsEnabled(viewId: Long): Boolean {
return getNavigationView(viewId.toInt()).isTrafficPromptsEnabled()
}
Expand Down
Loading
Loading