> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading InstantSearch Android

> Upgrade your InstantSearch Android version

export const FlavorSwitcher = ({current, baseHref = "", options = [], label = "InstantSearch framework"}) => {
  if (options.length === 0) {
    return <div className="not-prose" role="alert" style={{
      margin: "0.25rem 0 1.5rem",
      padding: "0.75rem",
      border: "1px solid #f59e0b",
      borderRadius: "0.625rem",
      color: "inherit",
      fontSize: "0.875rem"
    }}>
        FlavorSwitcher requires at least one option.
      </div>;
  }
  const selected = options.find(option => option.value === current) ?? options[0];
  return <div className="not-prose mint-flavor-switcher">
      <style>{`
        .mint-flavor-switcher {
          --mfs-bg: #ffffff;
          --mfs-bg-hover: #f4f4f5;
          --mfs-bg-current: #eef2ff;
          --mfs-border: #d4d4d8;
          --mfs-fg: #18181b;
          --mfs-muted: #71717a;
          --mfs-accent: #4f46e5;
          position: relative;
          width: min(100%, 19rem);
          margin: 0.25rem 0 1.5rem;
          color: var(--mfs-fg);
          font-size: 0.875rem;
          line-height: 1.25rem;
        }

        .dark .mint-flavor-switcher {
          --mfs-bg: #18181b;
          --mfs-bg-hover: #27272a;
          --mfs-bg-current: #272747;
          --mfs-border: #3f3f46;
          --mfs-fg: #fafafa;
          --mfs-muted: #a1a1aa;
          --mfs-accent: #a5b4fc;
        }

        .mint-flavor-switcher details {
          position: relative;
        }

        .mint-flavor-switcher summary {
          display: flex;
          min-height: 2.75rem;
          box-sizing: border-box;
          align-items: center;
          justify-content: space-between;
          gap: 0.75rem;
          padding: 0.625rem 0.75rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          color: var(--mfs-fg);
          cursor: pointer;
          font-weight: 600;
          list-style: none;
          transition: border-color 150ms ease, box-shadow 150ms ease;
        }

        .mint-flavor-switcher summary::-webkit-details-marker {
          display: none;
        }

        .mint-flavor-switcher summary:hover {
          border-color: var(--mfs-accent);
        }

        .mint-flavor-switcher summary:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: 2px;
        }

        .mint-flavor-switcher__label {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .mint-flavor-switcher__chevron {
          flex: none;
          transition: transform 150ms ease;
        }

        .mint-flavor-switcher details[open] .mint-flavor-switcher__chevron {
          transform: rotate(180deg);
        }

        .mint-flavor-switcher__menu {
          position: absolute;
          z-index: 50;
          top: calc(100% + 0.375rem);
          left: 0;
          width: 100%;
          box-sizing: border-box;
          margin: 0;
          padding: 0.375rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          box-shadow: 0 12px 30px rgb(0 0 0 / 16%);
          list-style: none;
        }

        .mint-flavor-switcher__menu li {
          margin: 0;
          padding: 0;
        }

        .mint-flavor-switcher__option {
          display: grid;
          gap: 0.125rem;
          padding: 0.625rem 0.75rem;
          border-radius: 0.4rem;
          color: var(--mfs-fg);
          text-decoration: none;
        }

        .mint-flavor-switcher__option:hover {
          background: var(--mfs-bg-hover);
        }

        .mint-flavor-switcher__option:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: -2px;
        }

        .mint-flavor-switcher__option[aria-current="page"] {
          background: var(--mfs-bg-current);
          color: var(--mfs-accent);
        }

        .mint-flavor-switcher__name {
          font-weight: 600;
        }

        .mint-flavor-switcher__description {
          color: var(--mfs-muted);
          font-size: 0.8125rem;
        }

        @media (prefers-reduced-motion: reduce) {
          .mint-flavor-switcher summary,
          .mint-flavor-switcher__chevron {
            transition: none;
          }
        }
      `}</style>

      <details>
        <summary aria-label={`${label}: ${selected.label}`}>
          <span className="mint-flavor-switcher__label">{selected.label}</span>
          <svg className="mint-flavor-switcher__chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </summary>

        <ul className="mint-flavor-switcher__menu" aria-label={label}>
          {options.map(option => {
    const isCurrent = option.value === selected.value;
    const href = option.href ?? `${baseHref.replace(/\/$/, "")}/${encodeURIComponent(option.value)}`;
    return <li key={option.value}>
                <a className="mint-flavor-switcher__option" href={href} aria-current={isCurrent ? "page" : undefined}>
                  <span className="mint-flavor-switcher__name">
                    {option.label}
                  </span>
                  {option.description ? <span className="mint-flavor-switcher__description">
                      {option.description}
                    </span> : null}
                </a>
              </li>;
  })}
        </ul>
      </details>
    </div>;
};

<div className="mint-flavor-switcher-slot not-prose">
  <FlavorSwitcher
    current="android"
    baseHref="/doc/guides/building-search-ui/upgrade-guides"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
{ value: "vue", label: "Vue", description: "Vue InstantSearch" },
{ value: "ios", label: "iOS", description: "InstantSearch iOS" },
{ value: "android", label: "Android", description: "InstantSearch Android" },
]}
  />
</div>

## Upgrade from InstantSearch v3 to v4

In InstantSearch v4.0.0 and later, InstantSearch uses Kotlin 2.2 and the Algolia Kotlin API client 3.x.
This client updates models and simplifies types.

```kotlin Kotlin icon=code theme={"system"}
val searcher = HitsSearcher(
  applicationID = "ALGOLIA_APPLICATION_ID",
  apiKey = "ALGOLIA_SEARCH_API_KEY",
  indexName = "indexName",
  isAutoSendingHitsViewEvents = true
)
```

<Warning>
  InstantSearch v4.0 is a major upgrade and includes breaking changes.
  It removes wrapper types and legacy searchers (Answers and Places), and updates Insights to v3 events.
</Warning>

## Requirements

InstantSearch Android 4.x has updated minimum requirements:

| Requirement          | 3.x  | 4.x  |
| -------------------- | ---- | ---- |
| Kotlin               | 1.9+ | 2.2+ |
| Android `minSdk`     | 21   | 23   |
| Android `compileSdk` | 33   | 35   |
| Ktor                 | 2.3  | 3.3  |
| Algolia client       | 2.x  | 3.x  |

## Wrapper types removed

The Kotlin API client 3.x removed primitive wrapper types. Use plain `String` values instead:

| Type           | 3.x                    | 4.x     |
| -------------- | ---------------------- | ------- |
| Application ID | `ApplicationID("...")` | `"..."` |
| API key        | `APIKey("...")`        | `"..."` |
| Index name     | `IndexName("...")`     | `"..."` |
| Attribute      | `Attribute("...")`     | `"..."` |
| Object ID      | `ObjectID("...")`      | `"..."` |
| Query ID       | `QueryID("...")`       | `"..."` |
| Event name     | `EventName("...")`     | `"..."` |
| User token     | `UserToken("...")`     | `"..."` |

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  val searcher = HitsSearcher(
      applicationID = ApplicationID("YourApplicationID"),
      apiKey = APIKey("YourSearchOnlyAPIKey"),
      indexName = IndexName("index_name")
  )
  ```

  ```kotlin After 4.x theme={"system"}
  val searcher = HitsSearcher(
      applicationID = "YourApplicationID",
      apiKey = "YourSearchOnlyAPIKey",
      indexName = "index_name"
  )
  ```
</CodeGroup>

## Search parameters

Replaces the `Query` class with `SearchParamsObject` from the Algolia Kotlin Client 3.x:

| Type              | 3.x                | 4.x                       |
| ----------------- | ------------------ | ------------------------- |
| Query             | `Query(...)`       | `SearchParamsObject(...)` |
| Search parameters | `SearchParameters` | `SearchParamsObject`      |

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  val query = Query(analytics = false)
  val searcher = HitsSearcher(
      applicationID = ApplicationID("YourApplicationID"),
      apiKey = APIKey("YourSearchOnlyAPIKey"),
      indexName = IndexName("index_name"),
      query = query
  )
  ```

  ```kotlin After 4.x theme={"system"}
  val query = SearchParamsObject(analytics = false)
  val searcher = HitsSearcher(
      applicationID = "YourApplicationID",
      apiKey = "YourSearchOnlyAPIKey",
      indexName = "index_name",
      query = query
  )
  ```
</CodeGroup>

## Response types

Search response types have been updated to match the Kotlin API client 3.x:

| Type            | 3.x                       | 4.x                            |
| --------------- | ------------------------- | ------------------------------ |
| Search response | `ResponseSearch`          | `SearchResponse`               |
| Facets response | `ResponseSearchForFacets` | `SearchForFacetValuesResponse` |
| Facet           | `Facet`                   | `FacetHits`                    |

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  val facet = Facet("red", 10)
  ```

  ```kotlin After 4.x theme={"system"}
  val facet = FacetHits("red", "", 10)
  ```
</CodeGroup>

## Filters

InstantSearch now owns its own filter model. Update your imports:

| Type            | 3.x                                               | 4.x                                                |
| --------------- | ------------------------------------------------- | -------------------------------------------------- |
| Filter          | `com.algolia.search.model.filter.Filter`          | `com.algolia.instantsearch.filter.Filter`          |
| FilterGroup     | `com.algolia.search.model.filter.FilterGroup`     | `com.algolia.instantsearch.filter.FilterGroup`     |
| NumericOperator | `com.algolia.search.model.filter.NumericOperator` | `com.algolia.instantsearch.filter.NumericOperator` |

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  Filter.Facet(Attribute("color"), "red")
  ```

  ```kotlin After 4.x theme={"system"}
  Filter.Facet("color", "red")
  ```
</CodeGroup>

## Request options

`RequestOptions` uses immutable construction from the Algolia Kotlin Client 3.x:

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  val requestOptions = RequestOptions().also {
      it.headers[KeyForwardedFor] = "1.2.3.4"
  }
  ```

  ```kotlin After 4.x theme={"system"}
  val requestOptions = RequestOptions(
      headers = mapOf("X-Forwarded-For" to "1.2.3.4")
  )
  ```
</CodeGroup>

## Insights

Insights has been updated to use v3 events and the `pushEvents` API:

| Type               | 3.x                        | 4.x                 |
| ------------------ | -------------------------- | ------------------- |
| Event construction | `InsightsEvent.Click(...)` | Direct method calls |
| Event names        | `EventName("...")`         | `"..."`             |
| User tokens        | `UserToken("...")`         | `"..."`             |

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  sharedInsights?.clicked(
      InsightsEvent.Click(
          eventName = EventName("eventName"),
          indexName = IndexName("indexName"),
          userToken = UserToken("userToken"),
          timestamp = System.currentTimeMillis(),
          queryID = QueryID("queryId"),
          resources = InsightsEvent.Resources.ObjectIDs(
              listOf(ObjectID("objectID1"))
          )
      )
  )
  ```

  ```kotlin After 4.x theme={"system"}
  sharedInsights?.clickedObjectIDsAfterSearch(
      eventName = "eventName",
      indexName = "indexName",
      userToken = "userToken",
      timestamp = System.currentTimeMillis(),
      queryID = "queryId",
      objectIDs = listOf("objectID1"),
      positions = listOf(1)
  )
  ```
</CodeGroup>

## Trackers

Tracker initialization now uses `String` for event names:

<CodeGroup>
  ```kotlin Before 3.x theme={"system"}
  val hitsTracker = HitsTracker(
      eventName = EventName("hits"),
      searcher = searcher,
      insights = sharedInsights(indexName)
  )
  ```

  ```kotlin After 4.x theme={"system"}
  val hitsTracker = HitsTracker(
      eventName = "hits",
      searcher = searcher,
      insights = sharedInsights(indexName)
  )
  ```
</CodeGroup>

## Indexable interface

Hit models for tracking must implement the `Indexable` interface:

```kotlin Kotlin icon=code theme={"system"}
import com.algolia.instantsearch.core.Indexable

data class Product(
    override val objectID: String,
    val name: String
) : Indexable
```

## Removed features

The following features have been removed in v4:

| Feature                                       | Status                                  |
| --------------------------------------------- | --------------------------------------- |
| `SearcherAnswers`                             | Removed (Answers deprecated by Algolia) |
| `SearcherPlaces`                              | Removed (Places deprecated by Algolia)  |
| Wrapper types (`Attribute`, `ObjectID`, etc.) | Removed (use `String`)                  |

## Dependencies

Update your dependencies in `build.gradle` or `gradle/libs.versions.toml`:

```kotlin theme={"system"}
// Kotlin
kotlin = "2.2.0"

// Algolia
implementation("com.algolia:instantsearch-android:4.0.0")
implementation("com.algolia:algoliasearch-client-kotlin:3.37.2")

// Ktor (if using directly)
ktor = "3.3.3"

// Compose (if using)
composeUi = "1.10.0"
composeCompiler = "2.2.0"
```

## Migration checklist

1. **Update dependencies** to the versions listed above
2. **Update `minSdk`** to 23 in your `build.gradle`
3. **Remove wrapper types**: replace `ApplicationID`, `APIKey`, `IndexName`, `Attribute`, `ObjectID`, `QueryID`, `EventName`, `UserToken` with plain `String`
4. **Update query types**: replace `Query` with `SearchParamsObject`
5. **Update response types**: replace `ResponseSearch` with `SearchResponse`, `Facet` with `FacetHits`
6. **Update filter imports**: use `com.algolia.instantsearch.filter.Filter` instead of `com.algolia.search.model.filter.Filter`
7. **Update Insights**: use v3 event methods instead of `InsightsEvent` constructors
8. **Implement `Indexable`**: ensure hit models implement `Indexable` for tracking
9. **Remove Answers/Places**: if using `SearcherAnswers` or `SearcherPlaces`, migrate to standard search
10. **Test thoroughly**: the new versions include significant internal changes

## Upgrade event tracking

**Starting from v3.2.0, InstantSearch makes it easier to send view events using the [`isAutoSendingHitsViewEvents`](/doc/api-reference/widgets/instantsearch/android#param-is-auto-sending-hits-view-events) option** on [`HitsSearcher`](/doc/api-reference/widgets/instantsearch/android#hitssearcher):

```kotlin Kotlin theme={"system"}
val searcher = HitsSearch(
  applicationID = ApplicationID("ALGOLIA_APPLICATION_ID"),
  apiKey = APIKey("ALGOLIA_SEARCH_API_KEY"),
  indexName = IndexName("indexName"),
  isAutoSendingHitsViewEvents = true
)
```

<Warning>
  From v3.2.0 to v3.3.0, this option was set to `true` by default. Ensure you're using the latest version of InstantSearch to send the required events.
</Warning>

## Upgrade from InstantSearch v2 to v3

The library version `3.0` uses Kotlin `1.6` and Algolia Kotlin API Client `2.0`. Below are the steps to migrate.

<Note>
  This new version removes all deprecated methods and features from `v2`.
</Note>

### InstantSearch Android package

InstantSearch Android package has changed. Update your imports accordingly:

| Module                | 2.x                                        | 3.x                                 |
| --------------------- | ------------------------------------------ | ----------------------------------- |
| InstantSearch Android | `com.algolia.instantsearch.helper.android` | `com.algolia.instantsearch.android` |

### Kotlin API client

The Kotlin API client (and its underlying Ktor client) is part of the library's binary interface. Read the [migration guide](https://github.com/algolia/algoliasearch-client-kotlin/blob/master/docs/guide/Migrate_1.x_2.x.md), or apply the following changes:

#### `LogLevel`

The library uses `LogLevel` from the Kotlin API client instead of Ktor's `Loglevel`:

| Subsystem | 2.x                                        | 3.x                                   |
| --------- | ------------------------------------------ | ------------------------------------- |
| LogLevel  | `io.ktor.client.features.logging.LogLevel` | `com.algolia.search.logging.LogLevel` |

#### Public constants

Constants (for example, `KeyIndexName`, `KeyEnglish`, and `RouteIndexesV1`) aren't exposed. Use your constants instead.

#### Ktor client

For more information, refer to Ktor's [migration guide](https://ktor.io/docs/migrating-2.html#feature-plugin-client).

### Searchers

Legacy searchers are removed. Migrate as follows:

| Searcher              | 2.x                     | 3.x              |
| --------------------- | ----------------------- | ---------------- |
| Single Index Searcher | `SearcherSingleIndex`   | `HitsSearcher`   |
| Facets Searcher       | `SearcherForFacets`     | `FacetsSearcher` |
| Multi Index Searcher  | `SearcherMultipleIndex` | `MultiSearcher`  |

### Extension modules

Multiple extensions have been extracted to modules:

| Subsystem                   | Module                                      |
| --------------------------- | ------------------------------------------- |
| AndroidX SwipeRefreshLayout | `com.algolia:instantsearch-android-loading` |
| AndroidX Paging 3           | `com.algolia:instantsearch-android-paging3` |
| AndroidX Paging 2           | Removed                                     |

## Upgrade from InstantSearch v1 to v2

InstantSearch v2 introduces a new architecture and new widgets, which brings several breaking changes from v1:

* **No longer an `InstantSearch` component** automatically connecting widgets. You are now in control of the `Searcher` and responsible for connecting and disconnecting it from widgets and other components.
* The widgets are now **built around `ViewModel`s**, holding their data and business logic.

  Compared to the V1 where widgets were Android `Views`, now the core of the widget is its view-model, and the UI is behind an interface to ensure minimal coupling.

You can learn more about the philosophy of InstantSearch v2 in [What is InstantSearch](/doc/guides/building-search-ui/what-is-instantsearch/android). Once ready to start migrating, learn the steps to building an InstantSearch v2 app in the [getting started guide](/doc/guides/building-search-ui/getting-started/android)..

<Note>
  The InstantSearch Android v1 documentation is available on the [legacy docs page](https://algolia.com/old-docs/deprecated/instantsearch/android/v1/api-ref/api-ref/).
</Note>
