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

# SDK — Native Android

> Step-by-step guide to integrating the LearnInk Mobile SDK into a native Android app

<Note>
  **Beta — installed from GitHub.** During the beta you build the SDK locally once, then
  depend on it like any other library. When we publish to **Maven Central** the only change
  will be removing `mavenLocal()` and the build step. See [Overview](/sdk/overview).
</Note>

By the end of this guide, tapping a button in your app opens LearnInk with the user
already signed in.

## Prerequisites

* Android Studio, with **JDK 17 or newer** (check in **Settings → Build, Execution,
  Deployment → Build Tools → Gradle → Gradle JDK**)
* Your app's `minSdk` is **21 or higher**
* A backend endpoint that returns a LearnInk sign-in token —
  see [Step 1 of the WebView setup guide](/integration/webview-setup-guide)
* Your **org ID**
* Access to [github.com/LearnInkTeam/learnink-sdk](https://github.com/LearnInkTeam/learnink-sdk)

<Steps>
  <Step title="Build the SDK on your machine">
    Clone the repository somewhere outside your app project, check out the beta tag, and
    publish the SDK to your local Maven repository:

    ```bash theme={null}
    git clone https://github.com/LearnInkTeam/learnink-sdk.git
    cd learnink-sdk
    git checkout v0.1.0
    cd android
    ./gradlew :sdk:publishToMavenLocal
    ```

    You should see `BUILD SUCCESSFUL`. This places the SDK in `~/.m2/repository/ink/learn/`,
    where Gradle can find it.

    <Warning>
      **Every developer on your team must run this once**, and so must your CI runner. It is the
      main inconvenience of the beta and it disappears when we publish to Maven Central. If your
      CI cannot clone the repository, see [Using a shared repository instead](#using-a-shared-repository-instead) below.
    </Warning>

    <Accordion title="Build fails with 'Gradle requires JVM 17 or later'">
      Your default JDK is too old. Either set `JAVA_HOME` for this command:

      ```bash theme={null}
      JAVA_HOME=$(/usr/libexec/java_home -v 21) ./gradlew :sdk:publishToMavenLocal
      ```

      or point Android Studio's Gradle JDK at 17+ and run it from there.
    </Accordion>
  </Step>

  <Step title="Add the dependency">
    In your app's **settings.gradle.kts**, add `mavenLocal()` to the repositories block:

    ```kotlin settings.gradle.kts theme={null}
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
            mavenLocal() // Beta only — remove once LearnInk publishes to Maven Central
        }
    }
    ```

    <Note>
      If your project uses the older style with `allprojects { repositories { … } }` in the root
      `build.gradle`, add `mavenLocal()` there instead.
    </Note>

    Then in your **app module's `build.gradle.kts`**:

    ```kotlin app/build.gradle.kts theme={null}
    dependencies {
        implementation("ink.learn:learnink-sdk:0.1.0")
    }
    ```

    Sync Gradle. If it cannot resolve the dependency, you missed Step 1 or added
    `mavenLocal()` in the wrong place — see [Troubleshooting](/sdk/troubleshooting).
  </Step>

  <Step title="Check your permissions">
    The SDK declares the permissions it needs (`INTERNET` and `ACCESS_NETWORK_STATE`) in its
    own manifest, and Android merges them into your app automatically. **You do not need to
    add anything.**

    `ACCESS_NETWORK_STATE` is used only to detect whether the device is online, so the SDK can
    show downloaded content immediately instead of waiting for a network request that cannot
    succeed.
  </Step>

  <Step title="Configure the SDK once, at startup">
    Tell the SDK your org ID and how to get a token. Do this once — in your `Application`
    class, or right after your own login completes:

    ```kotlin theme={null}
    import ink.learn.sdk.LearnInk
    import ink.learn.sdk.LearnInkConfig
    import ink.learn.sdk.TokenProvider

    LearnInk.configure(
        LearnInkConfig(orgId = "your-org-id"),
        TokenProvider.fromSuspend { fetchLearnInkToken() },
    )
    ```

    `fetchLearnInkToken()` is **your** function that calls **your** backend. Here is a
    complete example using OkHttp — adapt it to whatever HTTP client your app already uses:

    ```kotlin theme={null}
    private suspend fun fetchLearnInkToken(): String = withContext(Dispatchers.IO) {
        val body = JSONObject().put("id", currentUserId).toString()
            .toRequestBody("application/json".toMediaType())

        val request = Request.Builder()
            .url("https://your-api.example.com/auth/learnink")
            .post(body)
            // Authenticate this call however your own API expects:
            .header("Authorization", "Bearer $yourOwnUserSessionToken")
            .build()

        okHttpClient.newCall(request).execute().use { response ->
            check(response.isSuccessful) { "Token request failed: HTTP ${response.code}" }
            JSONObject(response.body!!.string()).getString("token")
        }
    }
    ```

    <Warning>
      **Never call the LearnInk Identify API directly from your app.** That would put your API
      key on user devices, where it can be extracted. Your app calls *your* backend; your
      backend holds the key. The SDK is built around this and gives you no way to do otherwise.
    </Warning>

    <Accordion title="Not using coroutines?">
      Implement `TokenProvider` directly and call the callback from whatever async mechanism you
      use. Call it exactly once:

      ```kotlin theme={null}
      val tokenProvider = TokenProvider { callback ->
          myApi.fetchLearnInkToken(
              onSuccess = { token -> callback.onSuccess(token) },
              onError = { error -> callback.onFailure(error) },
          )
      }
      ```
    </Accordion>
  </Step>

  <Step title="Open LearnInk">
    The simplest integration is one line. This opens a full-screen LearnInk experience that
    handles loading, closing, back navigation and session refresh for you:

    ```kotlin theme={null}
    LearnInk.open(context, path = "learning")
    ```

    `path` is the section to open — `"learning"` is the main training area.

    That is a complete integration. **If that is all you need, skip to Step 7.**
  </Step>

  <Step title="Optional — embed it in your own screen">
    If you want LearnInk inside your own layout (under a toolbar, in a tab, in a bottom
    sheet), use `LearnInkWebView` directly instead of `LearnInk.open`:

    ```kotlin theme={null}
    class LearningActivity : AppCompatActivity() {

        private lateinit var learnInk: LearnInkWebView

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            learnInk = LearnInkWebView(this).apply {
                eventListener = object : LearnInkEventListener {
                    override fun onCloseRequested() = finish()

                    override fun onOfflineContentUnavailable() = showNoOfflineContentScreen()
                }
            }

            findViewById<FrameLayout>(R.id.learnInkContainer).addView(learnInk)
            learnInk.load("learning")
        }

        override fun onDestroy() {
            learnInk.destroy() // Required — releases the WebView
            super.onDestroy()
        }
    }
    ```

    <Warning>
      Always call `destroy()` in `onDestroy()` (or `onDestroyView()` for a Fragment). Forgetting
      this leaks the WebView.
    </Warning>

    Two layout rules worth knowing:

    * Give the view a **real size** — in a `LinearLayout`, use `height = 0` with `layout_weight = 1`
      so it fills the space your toolbar does not use. A `wrap_content` height renders nothing.
    * **Position it clear of the system bars yourself.** The SDK does not apply window insets,
      because only your app knows where the view sits in your layout.
  </Step>

  <Step title="Verify it works">
    Run your app and open LearnInk. You should see:

    * ✅ A brief loading indicator, then LearnInk content
    * ✅ **No login screen** — the user is signed in automatically
    * ✅ Tapping the close button inside LearnInk dismisses the screen

    If you see a **login screen**, the token did not reach LearnInk. That is the most common
    first-run problem and it is almost always the backend endpoint — see
    [Troubleshooting](/sdk/troubleshooting).
  </Step>
</Steps>

## Reacting to events

Override only the callbacks you care about — they all have default empty implementations.

```kotlin theme={null}
learnInk.eventListener = object : LearnInkEventListener {
    override fun onCloseRequested() { finish() }
}
```

| Callback                           | When it fires                                | What you should do                                                |
| ---------------------------------- | -------------------------------------------- | ----------------------------------------------------------------- |
| `onCloseRequested()`               | User tapped close inside LearnInk            | Dismiss your screen                                               |
| `onAuthenticationFailed(error)`    | Token fetch failed; page loaded signed-out   | Log it — this usually means your backend is misconfigured         |
| `onSessionRefreshFailed(error)`    | Session expired and re-authentication failed | Offer a retry via `refreshSession()`                              |
| `onOfflineModeEntered()`           | Offline; showing downloaded content          | Usually nothing — LearnInk shows its own offline banner           |
| `onOfflineContentUnavailable()`    | Offline **and** nothing was downloaded       | **Show your own message** — see [Offline behaviour](/sdk/offline) |
| `onConnectivityRestored()`         | Network came back                            | Optionally call `refreshSession()`                                |
| `onMessage(type, rawJson)`         | A message the SDK does not handle itself     | Usually nothing                                                   |
| `onHistoryChanged(canGoBack)`      | WebView history changed                      | Update your own back button, if you have one                      |
| `onPageStarted` / `onPageFinished` | Page load lifecycle                          | Custom loading UI, analytics                                      |
| `onWebResourceError(description)`  | A page failed to load                        | Log it                                                            |

## Configuration reference

```kotlin theme={null}
LearnInkConfig(
    orgId = "your-org-id",
    baseUrl = "https://m.learn.ink",
    tokenTimeoutMillis = 5_000,
    openExternalLinksInBrowser = true,
    extraHttpHeaders = emptyMap(),
    offlineSupportEnabled = true,
    offlineFirstDelayMillis = 1_000,
)
```

| Option                       | Default               | What it does                                             |
| ---------------------------- | --------------------- | -------------------------------------------------------- |
| `orgId`                      | *required*            | Your LearnInk organisation ID                            |
| `baseUrl`                    | `https://m.learn.ink` | Only change if LearnInk gives you a staging URL          |
| `tokenTimeoutMillis`         | `5000`                | How long to wait for your `fetchToken` before giving up  |
| `openExternalLinksInBrowser` | `true`                | Links leaving LearnInk open in the device browser        |
| `extraHttpHeaders`           | empty                 | Extra headers on page loads (rarely needed)              |
| `offlineSupportEnabled`      | `true`                | Show downloaded content when offline                     |
| `offlineFirstDelayMillis`    | `1000`                | Show downloaded content if the token is slower than this |

## Using a shared repository instead

If running `publishToMavenLocal` on every machine and CI runner is impractical, publish the
SDK once to a Maven repository your team already has (Artifactory, Nexus, or a static
bucket), and point your app at that instead of `mavenLocal()`:

```bash theme={null}
cd learnink-sdk/android
./gradlew :sdk:publishReleasePublicationToLocalStagingRepository
# Upload the contents of sdk/build/staging-repo to your Maven repository
```

Email [support@learn.ink](mailto:support@learn.ink) if you would like help with this.

## Next steps

<CardGroup cols={2}>
  <Card title="Offline behaviour" icon="cloud-arrow-down" href="/sdk/offline">
    What your app must show when a user has no connection and nothing downloaded.
  </Card>

  <Card title="Push notifications" icon="bell" href="/integration/push-notifications">
    Still required — the SDK does not change how notifications reach users.
  </Card>
</CardGroup>
