# Build your custom data source connection screen

<figure><img src="/files/NyBhDC5gLTlMZnvImncA" alt="" width="188"><figcaption><p>If your UX requires a custom data source connection flow, you can easily build such screen using the Thryve SDK helper methods.</p></figcaption></figure>

## Connect/disconnect web data sources

There are two web links you need to allow your users to connect and disconnect their web data sources, respectively. To obtain these, use the `getConnectDataSourceUrl` and `getRevokeDataSourceUrl` methods of the SDK and specify the `dataSourceId` for the data source, you want to connect/disconnect.&#x20;

The following example shows usage of `getConnectDataSourceUrl`  and `getRevokeDataSourceUrl`  for connection/disconnection of Fitbit:

{% tabs %}
{% tab title="iOS" %}

```swift
import UIKit
import ThryveSDK
import ThryveCommons

/**
 Async function that fetches the connect‐URL for Fitbit via ThryveSDK and opens it in the default browser.

 - Data Source ID: `1` is hardcoded for Fitbit. Change if you need to connect a different data source.
 - Redirect URL: Specify your app’s custom URL scheme so the user can be sent back into the app after authorizing.
 - You can find more info about setting up schema url on: `https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app`
 */
@MainActor
func connectFitbit() async {
    // 1) Request the connect URL for Fitbit (dataSourceId = 1)
    let response = await ThryveSDK.get().getConnectDataSourceUrl(
        dataSourceId: 1,
        redirectUrl: "showThryve://"
    )

    if let url = response.data {
    // 2) Open the URL in Safari (or default browser)
      UIApplication.shared.open(url, options: [:], completionHandler: nil)
    } else {
    // 3) Surface or log the error(s)
      if let errors = response.errors {
        for error in errors {
          Logger.i { "Failed to connect \(providerName): \(error.errorMessage ?? "Unknown")" }
        }
      }
}


/**
 Async function that fetches the revoke‐URL for Fitbit via ThryveSDK and opens it in the default browser.

 - Data Source ID: `1` is hardcoded for Fitbit. Change if you need to disconnect a different data source.
 - `requireUserAction`: If `true`, the user will see a confirmation prompt in the browser before revocation.
 - Redirect URL: Specify your app’s custom URL scheme so the user can be sent back into the app after finishing the revocation flow.
 - You can find more info about setting up schema url on: `https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app`
 */
@MainActor
func disconnectFitbit() async {
    // 1) Request the revoke URL for Fitbit (dataSourceId = 1)
    let response = await ThryveSDK.get().getRevokeDataSourceUrl(
        dataSourceId: 1,
        requireUserAction: false,
        redirectUrl: "showThryve://"
    )
    
    if let url = response.data {
    // 2) Open the URL in Safari (or default browser)
      UIApplication.shared.open(url, options: [:], completionHandler: nil)
    } else {
    // 3) Surface or log the error(s)
      if let errors = response.errors {
        for error in errors {
          Logger.i { "Failed to disconnect \(providerName): \(error.errorMessage ?? "Unknown")" }
        }
      }
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.thryve.sdk.ThryveSDK
import com.thryve.sdk.model.ThryveResponse

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // … your layout inflation, etc.
    }

    /**
     * Async function that fetches the “connect” URL for Fitbit via ThryveSDK
     * and opens it in the default browser.
     *
     * - dataSourceId = 1 (Fitbit). Change for other sources.
     * - redirectUrl = "showThryve://" (your app’s deep‐link scheme).
     */
    private fun connectWebDataSource(context: Context) {
        // Launch a coroutine on the IO dispatcher so we don't get a NetworkOnMainThreadException
        viewModelScope.launch(Dispatchers.IO) {
            val response = thryveSDK.getConnectDataSourceUrl(
                dataSourceId = 1, // Use the Id of the preferred Web data source
                redirectUri = "showThryve://",
            )

            if (response.successful) {
                val intent = Intent(Intent.ACTION_VIEW, response.data?.toUri()).apply {
                    if (context !is Activity) {
                        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    }
                }

                // Switch to Main dispatcher for UI operations
                withContext(Dispatchers.Main) {
                    try {
                        context.startActivity(intent)
                    } catch (e: ActivityNotFoundException) {
                        Log.e(TAG, "❌ No browser app to handle connect URL", e)
                    }
                }
            } else {
                Log.e(TAG, "❌ Failed to retrieve connect URL: ${response.errors.joinToString()}")
                return@launch
            }
        }
    }

    /**
     * Async function that fetches the “revoke” URL for Fitbit via ThryveSDK
     * and opens it in the default browser.
     *
     * - dataSourceId = 1 (Fitbit). Change for other sources.
     * - requireUserAction = false (no confirmation prompt in browser).
     * - redirectUrl = "showThryve://".
     */
    private fun disconnectWebDataSource(context: Context) {
        // Launch a coroutine on the IO dispatcher so we don't get a NetworkOnMainThreadException
        viewModelScope.launch(Dispatchers.IO) {
            val response = thryveSDK.getRevokeDataSourceUrl(
                dataSourceId = 1,
                requireUserAction = false,
                redirectUri = "showThryve://"
            )
            }
            }

            if (response.successful) {
                val intent = Intent(Intent.ACTION_VIEW, response.data?.toUri()).apply {
                    if (context !is Activity) {
                        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    }
                }

                // Switch to Main dispatcher for UI operations
                withContext(Dispatchers.Main) {
                    try {
                        context.startActivity(intent)
                        Log.i(TAG, "✅ Revoke URL launched for ${source.source}")
                    } catch (e: ActivityNotFoundException) {
                        Log.e(TAG, "❌ No browser app to handle revoke URL", e)
                    }
                }
            } else {
                Log.e(TAG, "❌ Failed to retrieve revoke URL: ${response.errors.joinToString()}")
                return@launch
            }
        }
    }
}
```

{% endtab %}

{% tab title="React Native" %}

```javascript
// Assumes you have installed and linked: 
//   "thryve-sdk" and you import ThryveSDK from it. 
// Also import Linking from 'react-native'.

import { ThryveSDK } from '@thryve/react-native-sdk-module';
import { Linking } from 'react-native';

/**
 * Async function that fetches the "connect" URL for Fitbit via ThryveSDK
 * and opens it in the default browser.
 *
 * - dataSourceId: 1 (Fitbit). Change for other sources.
 * - redirectUrl: "showThryve://".
 */
export async function connectFitbit() {
  const sdk = new ThryveSDK().getOrCreate(thryvSDKConfig);
  try {
    const response = await sdk.getConnectDataSourceUrl(1, 'showThryve://');
    if (response?.data) {
      const url = response.data; // Should be a string or URL object
      await Linking.openURL(url.toString());
    } else {
      console.error('❌ Failed to retrieve Fitbit connect URL:', response.errors);
      // Optionally:  // Optionally: Alert your users of each error in response.errors
    }
  } catch (err) {
    console.error('❌ Unexpected error in connectFitbit():', err);
  }
}

/**
 * Async function that fetches the "revoke" URL for Fitbit via ThryveSDK
 * and opens it in the default browser.
 *
 * - dataSourceId: 1 (Fitbit). Change for other sources.
 * - requireUserAction: false (no confirmation prompt in browser).
 * - redirectUrl: "showThryve://".
 */
export async function disconnectFitbit() {
  const sdk = new ThryveSDK().getOrCreate(thryvSDKConfig);

  try {
    const response = await sdk.getRevokeDataSourceUrl(1, false, 'showThryve://');
    if (response?.data) {
      const url = response.data;
      await Linking.openURL(url.toString());
    } else {
      console.error('❌ Failed to retrieve Fitbit revoke URL:', response.errors);
      // Optionally: Alert your users of each error in response.errors
    }
  } catch (err) {
    console.error('❌ Unexpected error in disconnectFitbit():', err);
  }
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:flutter/material.dart';
import 'package:thryve_sdk/thryve_sdk.dart';
import 'package:url_launcher/url_launcher.dart';

/**
 * Async function that fetches the "connect" URL for Fitbit via ThryveSDK
 * and opens it in the default browser.
 *
 * - dataSourceId: 1 (Fitbit). Change for other sources.
 * - redirectUrl: "showThryve://".
 */
Future<void> connectFitbit() async {
  final sdk = ThryveSDK.getOrCreate();

  final response = await sdk.getConnectDataSourceUrl(
    dataSourceId: 1,
    redirectUrl: 'showThryve://',
  );

  if (response is ThryveResponseSuccess<Uri>) {
    final Uri url = response.value;
    // Open the URL in the default browser
    if (await canLaunchUrl(url)) {
      await launchUrl(url);
    } else {
      debugPrint('⚠️ Could not launch URL: $url');
    }
  } else if (response is ThryveResponseFailure<Uri>) {
    debugPrint('❌ Failed to retrieve Fitbit connect URL: ${response.error}');
  } else {
    debugPrint('❌ Unexpected response type for connectFitbit()');
  }
}

/**
 * Async function that fetches the "revoke" URL for Fitbit via ThryveSDK
 * and opens it in the default browser.
 *
 * - dataSourceId: 1 (Fitbit). Change for other sources.
 * - requireUserAction: false (no confirmation prompt in browser).
 * - redirectUrl: "showThryve://".
 */
Future<void> disconnectFitbit() async {
  final sdk = ThryveSDK.getOrCreate();

  final response = await sdk.getRevokeDataSourceUrl(
    dataSourceId: 1,
    requireUserAction: false,
    redirectUrl: 'showThryve://',
  );

  if (response is ThryveResponseSuccess<Uri>) {
    final Uri url = response.value;
    if (await canLaunchUrl(url)) {
      await launchUrl(url);
    } else {
      debugPrint('⚠️ Could not launch URL: $url');
    }
  } else if (response is ThryveResponseFailure<Uri>) {
    debugPrint('❌ Failed to retrieve Fitbit revoke URL: ${response.error}');
  } else {
    debugPrint('❌ Unexpected response type for disconnectFitbit()');
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
We recommend opening the link in an **external browser** and **not** a web view within your application. Web-views do not support social logins (e.g. Google, Facebook, or Apple sign-ins) that might be used by your end-users for certain data sources.
{% endhint %}

To make sure your user gets automatically forwarded back to your application after successfully connecting, make sure to specify a redirect URI using the `redirect_uri` parameter of the above SDK methods.

## Connect/disconnect native data sources

### Display only available native data source&#x20;

Not all native data sources are available on all devices, either because they are not installed or set up by the end user. Therefore, make sure to use the `isAvailable` method of the SDK to only display the data source if the method returns `true` to avoid errors.

### Connect and disconnect native data sources

Authorization of native data sources is happening on the operating system level. To make your users connect to Apple Health, Health Connect, or Samsung Health, simply call `start` for the corresponding source.&#x20;

{% hint style="info" %}
Ensure you have imported and configured the needed SDK modules.
{% endhint %}

The SDK will request access to the types specified in the `config` object and will take care of everything, and create the connection after authorization by the end-user.

{% tabs %}
{% tab title="iOS" %}

```javascript
import UIKit
import ThryveCore
import HealthKit
import ThryveCommons

/**
 Async function that starts Apple Health integration and requests authorization via ThryveSDK for a given set of HealthKit data types.

 - `source`: Always use `.apple` for HealthKit.
 - `types`: A `Set<AnyHashable>` of `ThryveAppleHealthConnectorDataType` values, e.g. `[.stepCount]`.
            Passing `nil` will default to whatever types were configured in your ThryveSDK configuration object.
 */
@MainActor
func connectAppleHealth(types: Set<AnyHashable>? = nil) async {
    // Passing `nil` here causes the SDK to request permissions for the types defined in its config.
    let response: ThryveResponse<Bool> = await ThryveSDK.get().start(
        dataSource: .apple,
        dataTypes: types
    )

    if response.successful {
      Logger .i { "✅ Apple Health integration started successfully (using configured types)." }
      return
    }

    if let errors = response.errors {
      for error in errors {
        Logger.i { "Failed to start Apple Health: \(error.errorMessage ?? "Unknown")" }
      }
    }
}


/**
 Async function that stops Apple Health integration via ThryveSDK.

 - `source`: Always use `.apple` for HealthKit.
 */
@MainActor
func disconnectAppleHealth() async {
    let response: ThryveResponse<Bool> = await ThryveSDK.get().stop(dataSource: .apple)

    if response.successful {
      Logger.i{ "✅ Apple Health integration stopped successfully." }
      return
    }

    if let errors = response.errors {
      for error in errors {
        Logger.i { "Failed to stop Apple Health: \(error.errorMessage ?? "Unknown")" }
      }
    }
}
```

{% endtab %}

{% tab title="Android" %}

```kotlin
import com.thryve.sdk.ThryveSDK
import com.thryve.sdk.healthConnect
import com.thryve.sdk.samsungHealth

...
fun connectHealthConnect(activity: ComponentActivity){
    ThryveSDK.get()?.start(activity, Source.HEALTH_CONNECT) {
        runOnUiThread {
            Logger.d(this@MainActivity.TAG) { "Health Connect start successful = ${it.successful} data = ${it.data}" }
            it.errors.map { error ->
                Logger.d(this@MainActivity.TAG) { "Health Connect start error $error" }
            }
        }
    }
}

fun disconnectHealthConnect(){
    ThryveSDK.get()?.stop(Source.HEALTH_CONNECT) {
        runOnUiThread {
            Logger.d(this@MainActivity.TAG) { "Health Connect stop successful = ${it.successful} data = ${it.data}" }
            it.errors.map { error ->
                Logger.d(this@MainActivity.TAG) { "Health Connect stop error $error" }
            }
        }
    }
}

fun connectSamsungHealth(activity: ComponentActivity){
    ThryveSDK.get()?.start(activity, Source.SAMSUNG) {
        runOnUiThread {
            Logger.d(this@MainActivity.TAG) { "Samsung Health start successful = ${it.successful} data = ${it.data}" }
            it.errors.map { error ->
                Logger.d(this@MainActivity.TAG) { "Samsung Health  start error $error" }
            }
        }
    }
}

fun disconnectSamsungHealth(){
    ThryveSDK.get()?.stop(Source.SAMSUNG) {
        runOnUiThread {
            Logger.d(this@MainActivity.TAG) { "Samsung Health stop successful = ${it.successful} data = ${it.data}" }
            it.errors.map { error ->
                Logger.d(this@MainActivity.TAG) { "Samsung Health  stop error $error" }
            }
        }
    }
}
...
```

{% endtab %}

{% tab title="React Native" %}

```javascript
import {ThryveSDK, Source} from '@thryve/react-native-sdk';

function connectAppleHealth() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.start(Source.APPLE, (result) => {
      console.log(`Apple Health start successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Apple Health start error ${error}`);
      });
  });
}

function disconnectAppleHealth() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.stop(Source.APPLE, (result) => {
      console.log(`Apple Health stop successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Apple Health stop error ${error}`);
      });
  });
}
  
function connectHealthConnect() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.start(Source.HEALTH_CONNECT, (result) => {
      console.log(`Health Connect start successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Health Connect start error ${error}`);
      });
  });
}

function disconnectHealthConnect() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.stop(Source.HEALTH_CONNECT, (result) => {
      console.log(`Health Connect stop successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Health Connect stop error ${error}`);
      });
  });
}

function connectSamsungHealth() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.start(Source.SAMSUNG, (result) => {
      console.log(`Samsung Health start successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Samsung Health start error ${error}`);
      });
  });
}

function disconnectSamsungHealth() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.stop(Source.SAMSUNG, (result) => {
      console.log(`Samsung Health stop successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Samsung Health stop error ${error}`);
      });
  });
}

function disconnectShenAI() {
  const thryveSDK = new ThryveSDK().getOrCreate(thryvSDKConfig);
  if (!thryveSDK) return;

  thryveSDK.stop(Source.SHENAI, (result) => {
      console.log(`Shen AI stop successful = ${result.data}`);
      result.errors.forEach((error) => {
        console.log(`Shen AI stop error ${error}`);
      });
  });
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
// Soon to be updated
```

{% endtab %}
{% endtabs %}

To disconnect the data source, simply call `stop` and the SDK will not retrieve any data and no further data will be stored until `start` is called again for the user.

{% hint style="warning" %}
After calling `stop` the connection to your app will still appear active in Apple Health.  Apple does not allow developers to revoke the connection, and only users can manually revoke access inside the Health app. The internal functionalities of the Thryve service, however, will make sure no new data is stored after `stop` is called until `start` is called again.
{% endhint %}

## Obtain the correct connection status

To accurately display the connection status of data sources to users, use `ThryveSDK.get().getUserInformation`. This method returns a list of `connectedSources` their IDs. Additionally, `connectedAt` provides the timestamp of when the user connected to each data source.

<figure><img src="/files/xGuWMfvUc7ygNoK5U66J" alt="" width="188"><figcaption><p>Example of button states that change depending on information returned by <code>getUserInformation</code> </p></figcaption></figure>

Use the retrieved information to update the interface. Modify button actions based on this data: allow users to disconnect from connected data sources and encourage them to connect to those that are not yet linked.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.thryve.health/integrate-your-mobile-app/connect-data-sources/build-your-custom-data-source-connection-screen.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
