> ## 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 — React Native

> Step-by-step guide to integrating the LearnInk Mobile SDK into a React Native app

<Note>
  **Beta — installed from GitHub.** During the beta you install the package from a local
  tarball and build its Android half once. When we publish to **npm** and **Maven Central**,
  both steps collapse into a single `npm install @learnink/react-native-sdk`. 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.

<Note>
  This package does **not** require `react-native-webview`. It wraps the native LearnInk
  Android SDK directly, so behaviour matches the other frameworks exactly.
</Note>

## Prerequisites

* React Native **0.71 or newer** (both old and new architecture are supported)
* **JDK 17 or newer** for the Android build
* Your app's `minSdkVersion` 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)

<Note>
  The SDK targets **Android**. Render `LearnInkWebView` only in your Android flow.
</Note>

<Steps>
  <Step title="Build the SDK's Android half">
    The package wraps a native Android library, which you build once on your machine:

    ```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`.

    <Warning>
      **Every developer on your team must run this once**, and so must your CI runner. This step
      disappears when we publish to Maven Central.
    </Warning>

    <Accordion title="Build fails with 'Gradle requires JVM 17 or later'">
      Your default JDK is too old:

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

  <Step title="Pack the JavaScript package">
    From the same clone, produce an installable tarball:

    ```bash theme={null}
    cd ../react-native/learnink-sdk
    npm install
    npm pack
    ```

    This creates **`learnink-react-native-sdk-0.1.0.tgz`** in that folder.

    <Note>
      We recommend the tarball over `npm install <path>`. A path install creates a symlink,
      which Metro does not resolve without extra configuration; the tarball installs a normal
      copy and just works. It also gives you a single file you can commit or share with your
      team so nobody else needs to run this step.
    </Note>
  </Step>

  <Step title="Install it into your app">
    From your app's root:

    ```bash theme={null}
    npm install /absolute/path/to/learnink-sdk/react-native/learnink-sdk/learnink-react-native-sdk-0.1.0.tgz
    ```

    Autolinking picks up the native module — **no manual linking required**. If your app
    registers packages explicitly, add `LearnInkSdkPackage()` to your `MainApplication`.
  </Step>

  <Step title="Let your app find the native library">
    Your Android build needs to resolve the library you published in Step 1. In your app's
    **`android/build.gradle`**:

    ```groovy android/build.gradle theme={null}
    allprojects {
        repositories {
            mavenLocal() // Beta only — remove once LearnInk publishes to Maven Central
        }
    }
    ```

    <Note>
      React Native templates already declare `google()` and `mavenCentral()` elsewhere, so you
      only need to add `mavenLocal()`.
    </Note>
  </Step>

  <Step title="Write your token function">
    Create a function that calls **your** backend and returns the token string:

    ```tsx theme={null}
    const fetchToken = async (): Promise<string> => {
      const response = await fetch('https://your-api.example.com/auth/learnink', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // Authenticate this call however your own API expects:
          Authorization: `Bearer ${yourOwnUserSessionToken}`,
        },
        body: JSON.stringify({ id: currentUserId }),
      });

      if (!response.ok) {
        throw new Error(`Token request failed: HTTP ${response.status}`);
      }
      const data = await response.json();
      return data.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.
    </Warning>
  </Step>

  <Step title="Render the component">
    ```tsx theme={null}
    import React, { useState } from 'react';
    import { Button, SafeAreaView, StyleSheet, View } from 'react-native';
    import { LearnInkWebView } from '@learnink/react-native-sdk';

    export default function LearningScreen() {
      const [visible, setVisible] = useState(false);

      if (!visible) {
        return <Button title="Open LearnInk" onPress={() => setVisible(true)} />;
      }

      return (
        <SafeAreaView style={StyleSheet.absoluteFill}>
          <LearnInkWebView
            orgId="your-org-id"
            path="learning"
            fetchToken={fetchToken}
            onClose={() => setVisible(false)}
            style={styles.webview}
          />
        </SafeAreaView>
      );
    }

    const styles = StyleSheet.create({
      webview: { flex: 1 },
    });
    ```

    <Warning>
      **Do not render `LearnInkWebView` inside a React Native `Modal`.** On the new
      architecture, touches do not reach native views hosted in a `Modal`, so LearnInk appears
      but does not respond to taps. Use a dedicated screen in your navigator, or a conditionally
      rendered full-screen `View` as above.
    </Warning>

    <Note>
      Always give the component `flex: 1` or explicit dimensions. With no height it renders
      nothing.
    </Note>
  </Step>

  <Step title="Verify it works">
    Run on an Android device or emulator. You should see:

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

    If you see a **login screen**, the token did not reach LearnInk — see
    [Troubleshooting](/sdk/troubleshooting).
  </Step>
</Steps>

## Controlling the view

Attach a ref to drive the component after mount:

```tsx theme={null}
import { useRef } from 'react';
import { LearnInkWebView, type LearnInkWebViewHandle } from '@learnink/react-native-sdk';

const learnInkRef = useRef<LearnInkWebViewHandle>(null);

<LearnInkWebView ref={learnInkRef} /* … */ />

// Later:
learnInkRef.current?.refreshSession(); // Re-authenticate and reload
learnInkRef.current?.goBack();         // Back through WebView history
```

## Props reference

| Prop                          | Default               | What it does                                                              |
| ----------------------------- | --------------------- | ------------------------------------------------------------------------- |
| `orgId`                       | *required*            | Your LearnInk organisation ID                                             |
| `path`                        | *required*            | Section to open, e.g. `"learning"`                                        |
| `fetchToken`                  | *required*            | `() => Promise<string>` — calls your backend                              |
| `baseUrl`                     | `https://m.learn.ink` | Only change if given a staging URL                                        |
| `tokenTimeoutMs`              | `5000`                | How long to wait for `fetchToken`                                         |
| `openExternalLinksInBrowser`  | `true`                | External links open in the device browser                                 |
| `extraHttpHeaders`            | `{}`                  | Extra headers on page loads (rarely needed)                               |
| `offlineSupportEnabled`       | `true`                | Show downloaded content when offline                                      |
| `offlineFirstDelayMs`         | `1000`                | Show downloaded content if the token is slower than this                  |
| `onClose`                     | —                     | User tapped close inside LearnInk                                         |
| `onAuthenticationFailed`      | —                     | Token fetch failed; page loaded signed-out                                |
| `onSessionRefreshFailed`      | —                     | Re-authentication failed; offer a retry                                   |
| `onOfflineModeEntered`        | —                     | Offline; showing downloaded content                                       |
| `onOfflineContentUnavailable` | —                     | Offline **and** nothing downloaded — [show your own screen](/sdk/offline) |
| `onConnectivityRestored`      | —                     | Network came back                                                         |
| `onMessage`                   | —                     | A message the SDK does not handle itself                                  |
| `style`                       | —                     | Standard React Native style                                               |

## 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>
