Beta
Search SDK for Android

Data providers

Public beta

The Mapbox Search SDK for Android is in public beta. All features and workflows are subject to change.

The Mapbox Search SDK for Android lets developers to attach their own places, POIs, and address information to both SearchEngine and CategorySearchEngine via data providers. Engines will index the provided data and use this data during forward geocoding and category search requests.

Overview

The following diagram helps explain the typical data provider flow:

┌─────────────┐   ┌───────────────────────┐       ┌──────────────┐      ┌─────────────────┐         ┌──────────────┐
│ Application │   │ DataProvidersRegistry │         EngineLayer │         DataProvider           │ SearchEngine │
└──────┬──────┘   └───────────┬───────────┘       └──────┬───────┘      └────────┬────────┘         └───────┬──────┘
                                                                                                        
           register data                                                                                
              provider                                                                                  
       ├─────────────────────▶│                                                                           
                                                                                                        
                                 create engine layer                                                    
                             ├─────────────────────────▶│                                                 
                                                                                                        
                             │ instance of engine layer │                                                 
                             │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                                                 
                                                                                                        
                                            register engine layer                                        
                             ├─────────────────────────────────────────────────▶│                          
                                                                                                        
                                                           update index in                              
                                                            engine layer                                
                                                       │◀──────────────────────┤                          
                                                                                                        
                                                       │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│                          
                                                                                                        
                                           completion notification                                       
                             │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                          
                                                                                                        
                                                        add engine layer to engine                        
                             ├────────────────────────────────────────────────────────────────────────────▶│
                                                                                                          
                             │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  ─ ─ ─ ─│
             completion                                                                                 
            notification                                                                                
       │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│                                                                           
                                                                                                        
                                                                                                        
                                                                                                        
                                 add new indexable record                                                 
       ├────────────────────────────────────────────────────────────────────────▶│                          
                                                                                                        
                                                           update index in                              
                                                            engine layer                                
                                                       │◀──────────────────────┤                          
                                                                                                        
                                                       │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│                          
                                                                                                        
                                 completion notification                                                  
       │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │                          
                                                                                                        
                                                                                                        
                                                                                                        
                                               search for "Washington"                                     
       ├───────────────────────────────────────────────────────────────────────────────────────────────────▶│
                                                                                                        
                                                                                 execute search request │
                                                                              │ ┌────────────────────────┤
                                                                              │ └───────────────────────▶│
                                                                                                        
                                                            search "Washington" across engine layer      
                                                       │◀─────────────────────────────────────────────────┤
                                                                                                         
                                                                names and IDs of matched records         
                                                       │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
                                                                                                        
                                 get data providers associated with matched records (if records found)    
                             │◀────────────────────────────────────────────────────────────────────────────┤
                                                                                                        
                                                           list of data providers                         
                             │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
                                                                                                        
                                                                                retrieve records by ID  
                                                                              │◀─────────────────────────┤
                                                                                                               
                                                                                    list of records     
                                                                              │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
                                                                                                        
                                            search results for "Washington"                                
       │◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ── ─ ─ ─ │

Indexable record

IndexableRecord stores place, POI, and address information that can later be used by both users and the Search SDK. The Search SDK uses information from IndexableRecord to prepare the engine layer and construct search results.

Retrieve IndexableRecord from SearchResult

You can retrieve IndexableRecord from SearchResult, if SearchResult has been constructed from IndexableRecord by casting the search result to the IndexableRecordSearchResult interface:

val searchResult: SearchResult = // ...
val record = (result as? IndexableRecordSearchResult)?.record

Data provider

IndexableDataProvider has two main responsibilities:

  1. Store IndexableRecords and provide read and write access so the Search SDK is able to retrieve and add records.
  2. Keep the engine layer up to date to guarantee all indexable records are available during search.

Data providers registry

IndexableDataProvidersRegistry stores all registered data providers that search engines might later get and use to retrieve IndexableRecords. Right now only global registry is available. To obtain an instance of global registry, initialize a MapboxSearchSdk object and then invoke the ServiceProvider.globalDataProvidersRegistry() method:

val globalRegistry = MapboxSearchSdk.serviceProvider.globalDataProvidersRegistry()
Priority

Each indexable record has an associated priority that helps search engines determine the ranking of search results constructed from indexable records. The priority of a record is equal to the priority of the data provider that the record is associated with.

Built-in data providers have a predefined priority. For custom data providers, you specify the priority of the data provider when registering the data provider to IndexableDataProvidersRegistry:

val dataProvider: IndexableDataProvider<out IndexableRecord> = // ...
val asyncTask = MapboxSearchSdk.serviceProvider.globalDataProvidersRegistry().register(
    dataProvider,
    priority = 200,
    callback = object : IndexableDataProvidersRegistry.Callback {
        override fun onSuccess() {
        }

        override fun onError(e: Exception) {
        }
    }
)

Engine layer

IndexableDataProviderEngineLayer is a search index that can be used by multiple search engines during search requests after processing. If you want search engines to be able to search across your data, add all necessary indexable records to the engine layer during the IndexableDataProvider.registerIndexableDataProviderEngineLayer() operation or later, when data will become available:

class CustomDataProvider : IndexableDataProvider<CustomIndexableRecord> {

    private val dataProviderEngineLayers: MutableList<IndexableDataProviderEngineLayer> = mutableListOf()
    private val records: MutableList<IndexableRecord> = mutableListOf()
    
    /**
     * Invoked when `CustomDataProvider` is registered to specific `IndexableDataProvidersRegistry`.
     */
    override fun registerIndexableDataProviderEngineLayer(
        dataProviderEngineLayer: IndexableDataProviderEngineLayer,
        executor: Executor,
        callback: CompletionCallback<Unit>
    ): AsyncOperationTask {
        synchronized(this) {
            dataProviderEngineLayers.add(dataProviderEngineLayer)
            dataProviderEngineLayer.addAll(records)
        }
        executor.execute {
            callback.onComplete(Unit)
        }
        return CompletedAsyncOperationTask
    }

    /** 
     * Invoked when search records become available.
     */
    private fun onDataLoaded(customData: List<CustomRecord>) {
        synchronized(this) {
            records.addAll(customData)
            dataProviderEngineLayers.forEach { layer ->
                layer.addAll(records)
            }
        }
    }

    // ...

    private object CompletedAsyncOperationTask : AsyncOperationTask {

        override val isDone: Boolean
            get() = true

        override val isCancelled: Boolean
            get() = false

        override fun cancel() {
            // Do nothing
        }
    }
}

IndexableDataProviderEngineLayer is thread-safe and can be called from different threads.

Consider using engine layer only from background thread

Even though in most cases changing the index used in the engine layer takes a few milliseconds, sometimes it may take longer for operation to complete (for example, when the engine layer is used by a search engine or when user adds more than 10,000 records).

Built-in data providers

The Mapbox Search SDK for Android has built-in data providers that are already attached to each instance of SearchEngine and CategorySearchEngine.

Favorites Data Provider

The main purpose of FavoritesDataProvider is to store each user's favorite places, POIs, and addresses. The Search SDK UI module uses the favorites data provider to show the user's favorites in the "Favorites" section of SearchBottomSheetView.

To obtain an instance of the favorites data provider, initialize a MapboxSearchSdk object and then invoke the ServiceProvider.favoritesDataProvider() method:

val favoritesDataProvider = MapboxSearchSdk.serviceProvider.favoritesDataProvider()

The priority of the favorite data provider is equal to MapboxSearchSdk.LAYER_PRIORITY_FAVORITES.

The favorite data provider stores data in FavoriteRecord. One unique feature of this record is that the user doesn't have to specify IndexableRecord.indexTokens. Index tokens will be constructed from IndexableRecord.address data.

Use the FavoritesDataProvider.getAll() method to get all favorite records:

val asyncTask = favoritesDataProvider.getAll(object : CompletionCallback<List<FavoriteRecord>> {
    override fun onComplete(result: List<FavoriteRecord>) {
    }

    override fun onError(e: Exception) {
    }
})
example
Favorites

Save a new favorite record, access all saved favorites, and receive notifications about changes to saved favorites.

History Data Provider

The main purpose of HistoryDataProvider is to keep information about recently accessed SearchResults. The Search SDK UI module uses data from the history data provider to prepare and show the "Recent searches" section in SearchResultsView as well as in SearchBottomSheetView.

To obtain an instance of the history data provider, initialize a MapboxSearchSdk object and then invoke the ServiceProvider.historyDataProvider() method:

val historyDataProvider = MapboxSearchSdk.serviceProvider.historyDataProvider()

The priority of the history data provider is equal to MapboxSearchSdk.LAYER_PRIORITY_HISTORY.

The history data provider stores data in HistoryRecord. Similar to FavoriteRecord, this record constructs IndexableRecord.indexTokens from IndexableRecord.address data. Also, HistoryRecord introduces new property: timestamp. HistoryDataProvider and the Search SDK UI module use this property to sort history records by relevance.

Use the HistoryDataProvider.getAll() method to get all history records:

val asyncTask = historyDataProvider.getAll(object : CompletionCallback<List<HistoryRecord>> {
    override fun onComplete(result: List<HistoryRecord>) {
    }

    override fun onError(e: Exception) {
    }
})
Restrict the maximum number of history records

HistoryDataProvider imposes restrictions on the maximum allowed number of history records. Exceeding this maximum will result in HistoryDataProvider removing the oldest records (those with lowest timestamp values). You can control the maximum number of records by specifying SearchSdkSettings.maxHistoryRecordsAmount during MapboxSearchSdk initialization:

val searchSdkSettings = SearchSdkSettings(maxHistoryRecordsAmount = 10)
MapboxSearchSdk.initialize(
    application = application,
    accessToken = BuildConfig.MAPBOX_API_TOKEN,
    searchSdkSettings = searchSdkSettings
)
example
Search history

Access saved search history records.

Custom data providers

The Mapbox Search SDK for Android allows you create your own data providers if you want to introduce custom POIs or addresses to the search engine during the search.

example
Custom data provider

Search across POIs provided by custom data provider

First, you should create a record class that will store all the data about places, POIs, and addresses that you have. Implement an IndexableRecord interface. We recommend using a Kotlin data class and @Parcelize annotation from the kotlin-parcelize plugin to ease implementation:

@Parcelize
data class CustomRecord(
    override val id: String,
    override val name: String,
    override val coordinate: Point?,
    override val type: SearchResultType,
    override val descriptionText: String?,
    override val address: SearchAddress?,
    override val routablePoints: List<RoutablePoint>?,
    override val categories: List<String>?,
    override val makiIcon: String?,
    override val metadata: SearchResultMetadata?,
    override val indexTokens: List<String>,
) : IndexableRecord

Then, implement an IndexableDataProvider interface that will be used as a source of custom records and will update the search engine index associated with the provided IndexableDataProviderEngineLayer instance:

interface IndexableDataProvider<R : IndexableRecord> {

    public val dataProviderName: String

    public fun registerIndexableDataProviderEngineLayer(
        dataProviderEngineLayer: IndexableDataProviderEngineLayer,
        executor: Executor,
        callback: CompletionCallback<Unit>
    ): AsyncOperationTask

    public fun unregisterIndexableDataProviderEngineLayer(
        dataProviderEngineLayer: IndexableDataProviderEngineLayer,
        executor: Executor,
        callback: CompletionCallback<Boolean>
    ): AsyncOperationTask

    public fun get(id: String, executor: Executor, callback: CompletionCallback<in R?>): AsyncOperationTask

    public fun getAll(executor: Executor, callback: CompletionCallback<List<R>>): AsyncOperationTask

    public fun contains(id: String, executor: Executor, callback: CompletionCallback<Boolean>): AsyncOperationTask

    public fun add(record: R, executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask

    public fun addAll(records: List<R>, executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask

    public fun update(record: R, executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask

    public fun remove(id: String, executor: Executor, callback: CompletionCallback<Boolean>): AsyncOperationTask

    public fun clear(executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask
}
Keep engine layer up-to-date

Search engines will only be able to search across custom data if you've added custom data to IndexableDataProviderEngineLayer. Make sure you've provided your data to the engine layer once data is available. Read more about engine layers in Engine layer section.

Register custom data provider to the global IndexableDataProvidersRegistry to make sure all search engines get IndexableDataProviderEngineLayer instance associated with this data provider (make sure you have initialized a MapboxSearchSdk object before invoking ServiceProvider.globalDataProvidersRegistry()). Do not forget to specify the priority of your data provider:

val custonDataProvider: CustomDataProvider = // ...
val asyncTask = MapboxSearchSdk.serviceProvider.globalDataProvidersRegistry().register(
    customDataProvider,
    priority = 200,
    callback = object : IndexableDataProvidersRegistry.Callback {
        override fun onSuccess() {
        }

        override fun onError(e: Exception) {
        }
    }
)
Unregister data providers when they are not needed

If you want to remove your custom data from search engine indexes, unregister your custom data provider from the global IndexableDataProvidersRegistry:

val asyncTask = MapboxSearchSdk.serviceProvider.globalDataProvidersRegistry().unregister(
    customDataProvider,
    callback = object : IndexableDataProvidersRegistry.Callback {
        override fun onSuccess() {
        }

        override fun onError(e: Exception) {
        }
    }
)