> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/android/search/llms.txt)

# Custom data provider

This example does not contain a UI. Results will be logged in Android Studio.

> **Note**
> 
> This example is a part of the [Search SDK for Android sample app](https://github.com/mapbox/mapbox-search-android). You can find the values for all referenced resources in the [`res`](https://github.com/mapbox/mapbox-search-android/tree/main/MapboxSearch/sample/src/main/res) directory. For example, see [`res/values/strings.xml`](https://github.com/mapbox/mapbox-search-android/tree/main/MapboxSearch/sample/src/main/res/values/strings.xml) for `R.string.*` references used in this example. The dependencies can be found [here.](https://github.com/mapbox/mapbox-navigation-android-examples/blob/main/app/build.gradle) The examples use View binding. [See setup documention if necessary.](https://developer.android.com/topic/libraries/view-binding)

**Java**

Title: `MainActivity.java`

[View on GitHub](https://github.com/mapbox/mapbox-search-android/blob/main/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderJavaExample.java)

```java
package com.mapbox.search.sample.api;

import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.mapbox.geojson.Point;
import com.mapbox.search.ApiType;
import com.mapbox.search.common.AsyncOperationTask;
import com.mapbox.search.common.CompletionCallback;
import com.mapbox.search.ResponseInfo;
import com.mapbox.search.SearchEngine;
import com.mapbox.search.SearchEngineSettings;
import com.mapbox.search.SearchOptions;
import com.mapbox.search.SearchSelectionCallback;
import com.mapbox.search.common.concurrent.SearchSdkMainThreadWorker;
import com.mapbox.search.record.FavoriteRecord;
import com.mapbox.search.record.IndexableDataProvider;
import com.mapbox.search.record.IndexableDataProviderEngine;
import com.mapbox.search.record.IndexableRecord;
import com.mapbox.search.result.SearchResult;
import com.mapbox.search.result.SearchResultType;
import com.mapbox.search.result.SearchSuggestion;
import com.mapbox.search.sample.R;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executor;

import kotlin.Unit;

public class CustomIndexableDataProviderJavaExample extends AppCompatActivity {

    private SearchEngine searchEngine;
    private AsyncOperationTask registerProviderTask;
    @Nullable
    private AsyncOperationTask searchRequestTask = null;

    private final InMemoryDataProvider<IndexableRecord> customDataProvider = new InMemoryDataProvider<>(
        Arrays.asList(
            createRecord("Let it be", Point.fromLngLat(27.575321258282806, 53.89025545661358)),
            createRecord("Laŭka", Point.fromLngLat(27.574862357961212, 53.88998973246244)),
            createRecord("Underdog", Point.fromLngLat(27.57573285942709, 53.89020312748444))
        )
    );

    private final SearchSelectionCallback searchCallback = new SearchSelectionCallback() {
        @Override
        public void onSuggestions(@NonNull List<SearchSuggestion> suggestions, @NonNull ResponseInfo responseInfo) {
            if (suggestions.isEmpty()) {
                Log.i("SearchApiExample", "No suggestions found");
            } else {
                Log.i("SearchApiExample", "Search suggestions: " + suggestions + ".\nSelecting first suggestion...");
                searchRequestTask = searchEngine.select(suggestions.get(0), this);
            }
        }

        @Override
        public void onResult(@NonNull SearchSuggestion suggestion, @NonNull SearchResult result, @NonNull ResponseInfo responseInfo) {
            Log.i("SearchApiExample", "Search result: " + result);
        }

        @Override
        public void onResults(@NonNull SearchSuggestion suggestion, @NonNull List<SearchResult> results, @NonNull ResponseInfo responseInfo) {
            Log.i("SearchApiExample", "Category search results: " + results);
        }

        @Override
        public void onError(@NonNull Exception e) {
            Log.i("SearchApiExample", "Search error", e);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        searchEngine = SearchEngine.createSearchEngineWithBuiltInDataProviders(
            ApiType.SEARCH_BOX,
            new SearchEngineSettings()
        );

        Log.i("SearchApiExample", "Start CustomDataProvider registering...");

        registerProviderTask = searchEngine.registerDataProvider(
            customDataProvider,
            new CompletionCallback<Unit>() {
                @Override
                public void onComplete(Unit result) {
                    Log.i("SearchApiExample", "CustomDataProvider is registered");
                    searchRequestTask = searchEngine.search(
                        "Underdog",
                        new SearchOptions.Builder()
                            .proximity(Point.fromLngLat(27.574862357961212, 53.88998973246244))
                            .build(),
                        searchCallback
                    );
                }

                @Override
                public void onError(@NonNull Exception e) {
                    Log.i("SearchApiExample", "Error during registering", e);
                }
            }
        );
    }

    @Override
    protected void onDestroy() {
        registerProviderTask.cancel();

        if (searchRequestTask != null) {
            searchRequestTask.cancel();
        }

        searchEngine.unregisterDataProvider(
            customDataProvider,
            new CompletionCallback<Unit>() {
                @Override
                public void onComplete(Unit result) {
                    Log.i("SearchApiExample", "CustomDataProvider is unregistered");
                }

                @Override
                public void onError(@NonNull Exception e) {
                    Log.i("SearchApiExample", "Error during unregistering", e);
                }
            }
        );
        super.onDestroy();
    }

    private IndexableRecord createRecord(String name, Point coordinate) {
        return new FavoriteRecord(
            UUID.randomUUID().toString(),
            name,
            null,
            null,
            null,
            Collections.emptyList(),
            null,
            coordinate,
            SearchResultType.POI,
            null
        );
    }

    private static class InMemoryDataProvider<R extends IndexableRecord> implements IndexableDataProvider<R> {

        private final List<IndexableDataProviderEngine> dataProviderEngines = new ArrayList<>();
        private final Map<String, R> records = new LinkedHashMap<>();

        private final Executor mainThreadExecutor = SearchSdkMainThreadWorker.INSTANCE.getMainExecutor();

        InMemoryDataProvider(List<R> records) {
            for (R record : records) {
                this.records.put(record.getId(), record);
            }
        }

        @NonNull
        @Override
        public String getDataProviderName() {
            return "SAMPLE_APP_CUSTOM_DATA_PROVIDER";
        }

        @Override
        public int getPriority() {
            return 200;
        }

        @NonNull
        @Override
        public AsyncOperationTask registerIndexableDataProviderEngine(
            @NonNull IndexableDataProviderEngine dataProviderEngine,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Unit> callback
        ) {
            dataProviderEngine.upsertAll(records.values());
            dataProviderEngines.add(dataProviderEngine);
            executor.execute(() -> callback.onComplete(Unit.INSTANCE));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask registerIndexableDataProviderEngine(
            @NonNull IndexableDataProviderEngine dataProviderEngine,
            @NonNull CompletionCallback<Unit> callback
        ) {
            return registerIndexableDataProviderEngine(dataProviderEngine, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask unregisterIndexableDataProviderEngine(
            @NonNull IndexableDataProviderEngine dataProviderEngine,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Boolean> callback
        ) {
            boolean isRemoved = dataProviderEngines.remove(dataProviderEngine);
            if (isRemoved) {
                dataProviderEngine.clear();
            }
            executor.execute(() -> callback.onComplete(isRemoved));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask unregisterIndexableDataProviderEngine(
            @NonNull IndexableDataProviderEngine dataProviderEngine,
            @NonNull CompletionCallback<Boolean> callback
        ) {
            return unregisterIndexableDataProviderEngine(dataProviderEngine, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask get(
            @NonNull String id,
            @NonNull Executor executor,
            @NonNull CompletionCallback<? super R> callback
        ) {
            executor.execute(() -> callback.onComplete(records.get(id)));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask get(@NonNull String id, @NonNull CompletionCallback<? super R> callback) {
            return get(id, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask getAll(@NonNull Executor executor, @NonNull CompletionCallback<List<R>> callback) {
            executor.execute(() -> callback.onComplete(new ArrayList<>(records.values())));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask getAll(@NonNull CompletionCallback<List<R>> callback) {
            return getAll(mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask contains(
            @NonNull String id,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Boolean> callback
        ) {
            executor.execute(() -> callback.onComplete(records.get(id) != null));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask contains(@NonNull String id, @NonNull CompletionCallback<Boolean> callback) {
            return contains(id, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask upsert(
            @NonNull R record,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Unit> callback
        ) {
            for (IndexableDataProviderEngine engine : dataProviderEngines) {
                engine.upsert(record);
            }
            records.put(record.getId(), record);
            executor.execute(() -> callback.onComplete(Unit.INSTANCE));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask upsert(@NonNull R record, @NonNull CompletionCallback<Unit> callback) {
            return upsert(record, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask upsertAll(
            @NonNull List<? extends R> records,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Unit> callback
        ) {
            for (IndexableDataProviderEngine engine : dataProviderEngines) {
                engine.upsertAll(records);
            }
            for (R record : records) {
                this.records.put(record.getId(), record);
            }
            executor.execute(() -> callback.onComplete(Unit.INSTANCE));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask upsertAll(@NonNull List<? extends R> records, @NonNull CompletionCallback<Unit> callback) {
            return upsertAll(records, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask remove(
            @NonNull String id,
            @NonNull Executor executor,
            @NonNull CompletionCallback<Boolean> callback
        ) {
            for (IndexableDataProviderEngine engine : dataProviderEngines) {
                engine.remove(id);
            }
            boolean isRemoved = records.remove(id) != null;
            executor.execute(() -> callback.onComplete(isRemoved));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask remove(@NonNull String id, @NonNull CompletionCallback<Boolean> callback) {
            return remove(id, mainThreadExecutor, callback);
        }

        @NonNull
        @Override
        public AsyncOperationTask clear(@NonNull Executor executor, @NonNull CompletionCallback<Unit> callback) {
            for (IndexableDataProviderEngine engine : dataProviderEngines) {
                engine.clear();
            }
            records.clear();
            executor.execute(() -> callback.onComplete(Unit.INSTANCE));
            return AsyncOperationTask.getCompleted();
        }

        @NonNull
        @Override
        public AsyncOperationTask clear(@NonNull CompletionCallback<Unit> callback) {
            return clear(mainThreadExecutor, callback);
        }
    }
}
```

**Kotlin**

Title: `MainActivity.kt`

[View on GitHub](https://github.com/mapbox/mapbox-search-android/blob/main/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/api/CustomIndexableDataProviderKotlinExample.kt)

```kt
package com.mapbox.search.sample.api

import android.util.Log
import com.mapbox.geojson.Point
import com.mapbox.search.ApiType
import com.mapbox.search.ResponseInfo
import com.mapbox.search.SearchEngine
import com.mapbox.search.SearchEngineSettings
import com.mapbox.search.SearchOptions
import com.mapbox.search.SearchSelectionCallback
import com.mapbox.search.common.AsyncOperationTask
import com.mapbox.search.common.CompletionCallback
import com.mapbox.search.record.FavoriteRecord
import com.mapbox.search.record.IndexableDataProvider
import com.mapbox.search.record.IndexableDataProviderEngine
import com.mapbox.search.record.IndexableRecord
import com.mapbox.search.result.SearchResult
import com.mapbox.search.result.SearchResultType
import com.mapbox.search.result.SearchSuggestion
import com.mapbox.search.sample.R
import java.util.UUID
import java.util.concurrent.Executor

class CustomIndexableDataProviderKotlinExample : BaseKotlinExampleActivity() {

    override val titleResId: Int = R.string.action_open_custom_data_provider_kt_example

    private var searchEngine: SearchEngine? = null
    private var registerProviderTask: AsyncOperationTask? = null
    private var searchRequestTask: AsyncOperationTask? = null

    private val customDataProvider = InMemoryDataProvider(
        records = listOf(
            createRecord("Let it be", Point.fromLngLat(27.575321258282806, 53.89025545661358)),
            createRecord("Laŭka", Point.fromLngLat(27.574862357961212, 53.88998973246244)),
            createRecord("Underdog", Point.fromLngLat(27.57573285942709, 53.89020312748444)),
        )
    )

    private val searchCallback = object : SearchSelectionCallback {

        override fun onSuggestions(suggestions: List<SearchSuggestion>, responseInfo: ResponseInfo) {
            if (suggestions.isEmpty()) {
                logI("SearchApiExample", "No suggestions found", suggestions)
                onFinished()
            } else {
                logI("SearchApiExample", "Search suggestions: ${prettify(suggestions)}\nSelecting first suggestion...")
                searchRequestTask = searchEngine?.select(suggestions.first(), this)
            }
        }

        override fun onResult(
            suggestion: SearchSuggestion,
            result: SearchResult,
            responseInfo: ResponseInfo
        ) {
            logI("SearchApiExample", "Search result", result)
            onFinished()
        }

        override fun onResults(
            suggestion: SearchSuggestion,
            results: List<SearchResult>,
            responseInfo: ResponseInfo
        ) {
            logI("SearchApiExample", "Category search results", results)
            onFinished()
        }

        override fun onError(e: Exception) {
            logE("SearchApiExample", "Search error", e)
            onFinished()
        }
    }

    override fun startExample() {
        // Set your Access Token here if it's not already set in some other way
        // MapboxOptions.accessToken = "<my-access-token>"
        searchEngine = SearchEngine.createSearchEngineWithBuiltInDataProviders(
            ApiType.SEARCH_BOX,
            SearchEngineSettings()
        )

        Log.i("SearchApiExample", "Start CustomDataProvider registering...")
        registerProviderTask = searchEngine?.registerDataProvider(
            dataProvider = customDataProvider,
            callback = object : CompletionCallback<Unit> {
                override fun onComplete(result: Unit) {
                    logI("SearchApiExample", "CustomDataProvider is registered")
                    searchRequestTask = searchEngine?.search(
                        "Underdog",
                        SearchOptions(
                            proximity = Point.fromLngLat(27.574862357961212, 53.88998973246244),
                        ),
                        searchCallback
                    )
                }

                override fun onError(e: Exception) {
                    logE("SearchApiExample", "Error during registering", e)
                    onFinished()
                }
            }
        )
    }

    override fun onDestroy() {
        registerProviderTask?.cancel()
        searchRequestTask?.cancel()
        searchEngine?.unregisterDataProvider(
            dataProvider = customDataProvider,
            callback = object : CompletionCallback<Unit> {
                override fun onComplete(result: Unit) {
                    logI("SearchApiExample", "CustomDataProvider is unregistered")
                }

                override fun onError(e: Exception) {
                    logE("SearchApiExample", "Error during unregistering", e)
                }
            }
        )
        super.onDestroy()
    }

    private fun createRecord(name: String, coordinate: Point): IndexableRecord {
        return FavoriteRecord(
            UUID.randomUUID().toString(),
            name,
            null,
            null,
            null,
            emptyList(),
            null,
            coordinate,
            SearchResultType.POI,
            null,
        )
    }

    private class InMemoryDataProvider<R : IndexableRecord>(records: List<R>) : IndexableDataProvider<R> {

        private val dataProviderEngines: MutableList<IndexableDataProviderEngine> = mutableListOf()
        private val records: MutableMap<String, R> = mutableMapOf()

        override val dataProviderName: String = "SAMPLE_APP_CUSTOM_DATA_PROVIDER"
        override val priority: Int = 200

        init {
            this.records.putAll(records.map { it.id to it })
        }

        override fun registerIndexableDataProviderEngine(
            dataProviderEngine: IndexableDataProviderEngine,
            executor: Executor,
            callback: CompletionCallback<Unit>
        ): AsyncOperationTask {
            dataProviderEngine.upsertAll(records.values.toList())
            dataProviderEngines.add(dataProviderEngine)
            executor.execute {
                callback.onComplete(Unit)
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun unregisterIndexableDataProviderEngine(
            dataProviderEngine: IndexableDataProviderEngine,
            executor: Executor,
            callback: CompletionCallback<Boolean>
        ): AsyncOperationTask {
            val isRemoved = dataProviderEngines.remove(dataProviderEngine)
            if (isRemoved) {
                dataProviderEngine.clear()
            }
            executor.execute {
                callback.onComplete(isRemoved)
            }
            return AsyncOperationTask.COMPLETED
        }

        override operator fun get(
            id: String,
            executor: Executor,
            callback: CompletionCallback<in R?>
        ): AsyncOperationTask {
            executor.execute {
                callback.onComplete(records[id])
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun getAll(executor: Executor, callback: CompletionCallback<List<R>>): AsyncOperationTask {
            executor.execute {
                callback.onComplete(ArrayList(records.values))
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun contains(
            id: String,
            executor: Executor,
            callback: CompletionCallback<Boolean>
        ): AsyncOperationTask {
            executor.execute {
                callback.onComplete(records[id] != null)
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun upsert(record: R, executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask {
            dataProviderEngines.forEach {
                it.upsert(record)
            }
            records[record.id] = record
            executor.execute {
                callback.onComplete(Unit)
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun upsertAll(
            records: List<R>,
            executor: Executor,
            callback: CompletionCallback<Unit>
        ): AsyncOperationTask {
            dataProviderEngines.forEach {
                it.upsertAll(records)
            }
            for (record in records) {
                this.records[record.id] = record
            }
            executor.execute {
                callback.onComplete(Unit)
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun remove(id: String, executor: Executor, callback: CompletionCallback<Boolean>): AsyncOperationTask {
            dataProviderEngines.forEach {
                it.remove(id)
            }
            val isRemoved = records.remove(id) != null
            executor.execute {
                callback.onComplete(isRemoved)
            }
            return AsyncOperationTask.COMPLETED
        }

        override fun clear(executor: Executor, callback: CompletionCallback<Unit>): AsyncOperationTask {
            dataProviderEngines.forEach {
                it.clear()
            }
            records.clear()
            executor.execute {
                callback.onComplete(Unit)
            }
            return AsyncOperationTask.COMPLETED
        }
    }
}
```