Skip to content

Search#

Preview - requires Android 3.0.0-beta01, or iOS 3.3.0-beta.2 or higher

Add a full drop-in and customisable place search experience, in only a few lines of code. The component also allows choosing the start and end from a map, and optionally showing inline Route results.

Integration#

See the code examples below, or for examples in the sample apps, see

Android: https://github.com/citymapper/sdk-samples/blob/3.3.0-alpha/android/journey_planner_views/src/main/java/com/example/simpletransportviews/screens/getmesomewhere/GetMeSomewhereFragment.kt

iOS: https://github.com/citymapper/sdk-samples/blob/3.3.0-alpha/ios/journey_planner_swiftui/CitymapperUISample/UI/View/RouteListView.swift

Quick start#

// Or BottomSheetSearchWithMapViewController for the UIKit wrapper
BottomSheetSearchWithMapView(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus: .center(defaultPlace),
    searchProviderFactory: appleSearchProviderFactory(
        // Bias the search results to a known relevant region
        region: MKCoordinateRegion(center: bigBen,
                                    span: LocationConstants.defaultSpan)),
    dismissAction: {
        // Back button action
    },
    searchDidComplete: { routePlanningSpec in
        // Do something with the resulting start/end
    }
)
BottomSheetSearchWithMapLayout(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus = MapFocus.onPoint(defaultPlace),
    searchProviderFactory = googleSearchProviderFactory(
        googlePlacesApiKey = "...",
        // Bias the search results to a known relevant region
        region = regionBounds
    ),
    onSearchCompleted = { routePlanningSpec ->
        // Do something with the resulting start/end
    }
)
<com.citymapper.sdk.ui.search.view.BottomSheetSearchWithMapView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/search_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val searchView = 
    view.requireViewById<BottomSheetSearchWithMapView>(R.id.search_view)
searchView.configure(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus = MapFocus.onPoint(defaultPlace),
    searchProviderFactory = googleSearchProviderFactory(
        googlePlacesApiKey = "...",
        // Bias the search results to a known relevant region
        region = regionBounds
    ),
    onSearchCompleted = { routePlanningSpec ->
        // Do something with the resulting start/end
    }
)
}

Inline results#

The layout can be customised to show a top app bar and inline route results, instead of needing to navigate away to plan routes.

BottomSheetSearchWithMapView(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus: .center(defaultPlace),
    searchProviderFactory: appleSearchProviderFactory(
        region: MKCoordinateRegion(center: bigBen,
                                    span: LocationConstants.defaultSpan)),
    dismissAction: {
        // Back button action
    },
    searchCompleteView: { scope in
        scope.routeResults(
            planBuilder: { builder in
                builder.walkRoute()
                builder.scooterRoute()
                builder.transitRoutes()
            },
            didTapRoute: { route in
                if DirectionsViewController.supportsRoute(route: route) {
                    // Open route details using DirectionsViewController
                } else {
                    // Open route details using RouteDetailsContainer.createWrappedRouteDetailsController
                }
            }
        )
    }
)
BottomSheetSearchWithMapLayout(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus = MapFocus.onPoint(defaultPlace),
    searchProviderFactory = googleSearchProviderFactory(
        googlePlacesApiKey = "...",
        // Bias the search results to a known relevant region
        region = regionBounds
    ),
    topAppBarContent = {
        DefaultTopAppBar(onNavigateUp = { finish() })
    },
    searchCompleteContent = {
        // This is the content shown when the search completes
        // IMPORTANT - the gradle module must have the Jetpack compose compiler enabled,
        // as detailed at https://developer.android.com/jetpack/compose/setup#setup-compose

        // An example of customising this with your own state
        val myExampleState by stateStoreStateFlow.collectAsState()
        RouteResults(planBuilder = {
            // Customise the routes planned. This lambda is aware of compose state,
            // so any state changes read here will cause the routes to update
            if (myExampleState) { 
                walkRoute()
                bikeRoute()
                transitRoutes()
            } else {
                // Show some different combination of routes
            }
        }, onRouteClick = { route ->
            if (CitymapperDirectionsView.supportsRoute(route)) {
                // Show route detail using CitymapperDirectionsView
            } else {
                // Show route detail using RouteDetail.showStandaloneRouteDetailScreen
            }
        })
    }
)
// IMPORTANT! The `topAppBarContent` and `sheetContent` parameters take
// Composables, not views. For that reason, to customise the search layout,
// the gradle module must have the Jetpack compose compiler enabled,
// as detailed at https://developer.android.com/jetpack/compose/setup#setup-compose
searchView.configure(
    // A fallback location for the map center e.g. if no location permission
    // is granted
    defaultMapFocus = MapFocus.onPoint(defaultPlace),
    searchProviderFactory = googleSearchProviderFactory(
        googlePlacesApiKey = "...",
        // Bias the search results to a known relevant region
        region = regionBounds
    ),
    topAppBarContent = {
        DefaultTopAppBar(onNavigateUp = { finish() })
    },
    searchCompleteContent = {
        // This is the content shown when the search completes
        // IMPORTANT - the gradle module must have the Jetpack compose compiler enabled,
        // as detailed at https://developer.android.com/jetpack/compose/setup#setup-compose

        // An example of customising this with your own state
        val myExampleState by stateStoreStateFlow.collectAsState()
        RouteResults(planBuilder = {
            // Customise the routes planned. This lambda is aware of compose state,
            // so any state changes read here will cause the routes to update
            if (myExampleState) { 
                walkRoute()
                bikeRoute()
                transitRoutes()
            } else {
                // Show some different combination of routes
            }
        }, onRouteClick = {
            if (CitymapperDirectionsView.supportsRoute(route)) {
                // Show route detail using CitymapperDirectionsView
            } else {
                // Show route detail using RouteDetail.showStandaloneRouteDetailScreen
            }   
        })
    }
)