Skip to main content
  • Android SDK 28+ (Android 9.0+)
  • Kotlin 2.1.0+
  • Gradle 8.x+
  • Java 17
  • Environment ID from Dynamic Dashboard

Install the SDK

Step 1: Download AAR files

Download the latest AAR files from the Dynamic Android SDK repository:
  • dynamic-sdk-android.aar (required)
  • solana-web3.aar (optional - only if you need Solana support)
Place them in your project’s app/libs directory.

Step 2: Add dependencies

Add the Dynamic SDK and all required dependencies to your build.gradle.kts:
build.gradle.kts
dependencies {
    // ==========================================
    // Dynamic SDK AAR Files
    // ==========================================

    // Core SDK (required)
    implementation(files("libs/dynamic-sdk-android.aar"))

    // Solana SDK (optional - remove if not needed)
    implementation(files("libs/solana-web3.aar"))
    implementation("org.sol4k:sol4k:0.6.0")  // Required by Dynamic Solana SDK

    // ==========================================
    // Required transitive dependencies
    // ==========================================

    // Kotlin Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")

    // JSON serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // Android WebView
    implementation("androidx.webkit:webkit:1.8.0")

    // HTTP client
    implementation("com.squareup.okhttp3:okhttp:4.12.0")

    // Custom Tabs for authentication
    implementation("androidx.browser:browser:1.7.0")

    // Secure storage - DataStore + Tink
    implementation("androidx.datastore:datastore-preferences:1.1.1")
    implementation("com.google.crypto.tink:tink-android:1.15.0")

    // Passkeys support
    implementation("androidx.credentials:credentials:1.2.2")
    implementation("androidx.credentials:credentials-play-services-auth:1.2.2")
    implementation("com.google.android.gms:play-services-auth:20.7.0")

    // Jetpack Compose (required for DynamicUI)
    implementation(platform("androidx.compose:compose-bom:2024.09.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.foundation:foundation")
    implementation("androidx.compose.runtime:runtime")
    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
}
If you don’t need Solana support, you can skip the solana-web3.aar and sol4k dependencies.

Configure AndroidManifest.xml

Add the required permissions and activities to your AndroidManifest.xml:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:usesCleartextTraffic="true"
        ...>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- OAuth Callback Activity (Required) -->
        <activity
            android:name="com.dynamic.sdk.android.Auth.AuthCallbackActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:noHistory="true"
            android:theme="@android:style/Theme.Translucent.NoTitleBar">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="yourappscheme" />
            </intent-filter>
        </activity>
    </application>
</manifest>
Make sure the android:scheme in your manifest matches the redirectUrl in your ClientProps. For example, if redirectUrl = "myapp://", then use android:scheme="myapp".

Initialize the SDK

Initialize the Dynamic SDK in your MainActivity:
MainActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.ui.Modifier
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.UI.DynamicUI
import com.dynamic.sdk.android.core.ClientProps
import com.dynamic.sdk.android.core.LoggerLevel

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

        // Initialize Dynamic SDK
        val props = ClientProps(
            environmentId = "your-environment-id",
            appLogoUrl = "https://your-app.com/logo.png",
            appName = "Your App Name",
            redirectUrl = "yourappscheme://",
            appOrigin = "https://your-app.com",
            logLevel = LoggerLevel.DEBUG
        )
        DynamicSDK.initialize(props, applicationContext, this)

        setContent {
            MaterialTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Box(modifier = Modifier.fillMaxSize()) {
                        // Main app content
                        AppContent()

                        // Dynamic SDK WebView overlay (required for auth flows)
                        DynamicUI()
                    }
                }
            }
        }
    }
}

Configuration Options

The ClientProps supports various configuration options:
PropertyDescriptionRequired
environmentIdYour Dynamic environment ID from the dashboardYes
appNameYour app’s display nameYes
appLogoUrlURL to your app’s logo (shown in auth UI)Yes
redirectUrlDeep link URL scheme for OAuth callbacksYes
appOriginYour app’s origin URLYes
logLevelLogging level (.DEBUG, .INFO, .WARN, .ERROR)No

Access the SDK

After initialization, access the SDK singleton anywhere in your app:
val sdk = DynamicSDK.getInstance()

// Show auth modal
sdk.ui.showAuth()

// Check auth state
val isAuthenticated = sdk.auth.isAuthenticated()

// Get wallets
val wallets = sdk.wallets.userWallets

// Get primary wallet
val primaryWallet = sdk.wallets.primaryWallet

Basic Usage

Using Built-in Authentication UI

The easiest way to add authentication is using the built-in UI:
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.dynamic.sdk.android.DynamicSDK

@Composable
fun LoginScreen() {
    val sdk = DynamicSDK.getInstance()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        Button(
            onClick = { sdk.ui.showAuth() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Sign In")
        }
    }
}

Listening for Authentication State

Use Kotlin Flow to react to authentication changes:
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.UserProfile
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class AuthViewModel : ViewModel() {
    private val sdk = DynamicSDK.getInstance()

    private val _user = MutableStateFlow<UserProfile?>(null)
    val user: StateFlow<UserProfile?> = _user

    private val _isAuthenticated = MutableStateFlow(false)
    val isAuthenticated: StateFlow<Boolean> = _isAuthenticated

    init {
        // Check initial auth state
        _isAuthenticated.value = sdk.auth.isAuthenticated()
        _user.value = sdk.auth.authenticatedUser

        // Listen for auth state changes
        viewModelScope.launch {
            sdk.auth.authenticatedUserChanges.collect { user ->
                _user.value = user
                _isAuthenticated.value = user != null
            }
        }
    }
}

@Composable
fun AppContent(viewModel: AuthViewModel) {
    val isAuthenticated by viewModel.isAuthenticated.collectAsState()

    if (isAuthenticated) {
        HomeScreen()
    } else {
        LoginScreen()
    }
}

Complete Example with Wallets

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class HomeViewModel : ViewModel() {
    private val sdk = DynamicSDK.getInstance()

    private val _wallets = MutableStateFlow<List<BaseWallet>>(emptyList())
    val wallets: StateFlow<List<BaseWallet>> = _wallets

    init {
        // Get current wallets
        _wallets.value = sdk.wallets.userWallets

        // Listen for wallet changes
        viewModelScope.launch {
            sdk.wallets.userWalletsChanges.collect { newWallets ->
                _wallets.value = newWallets
            }
        }
    }

    fun showUserProfile() {
        sdk.ui.showUserProfile()
    }

    fun logout() {
        viewModelScope.launch {
            sdk.auth.logout()
        }
    }
}

@Composable
fun HomeScreen(viewModel: HomeViewModel) {
    val wallets by viewModel.wallets.collectAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "Welcome!",
            style = MaterialTheme.typography.headlineMedium
        )

        Spacer(modifier = Modifier.height(16.dp))

        if (wallets.isEmpty()) {
            CircularProgressIndicator()
            Text("Creating wallets...")
        } else {
            LazyColumn {
                items(wallets) { wallet ->
                    WalletCard(wallet)
                }
            }
        }

        Spacer(modifier = Modifier.height(16.dp))

        Button(
            onClick = { viewModel.showUserProfile() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Show Profile")
        }

        Button(
            onClick = { viewModel.logout() },
            modifier = Modifier.fillMaxWidth(),
            colors = ButtonDefaults.buttonColors(
                containerColor = MaterialTheme.colorScheme.error
            )
        ) {
            Text("Logout")
        }
    }
}

@Composable
fun WalletCard(wallet: BaseWallet) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 8.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = wallet.chain.uppercase(),
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.primary
            )
            Text(
                text = wallet.address,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

Enable Features in Dashboard

Before you can use authentication and wallet features, enable them in your Dynamic dashboard:
  1. Go to your Dynamic Dashboard
  2. Select your project
  3. Go to Authentication and enable the methods you want to use:
    • Email OTP
    • SMS OTP
    • Social providers (Google, Apple, Farcaster, etc.)
    • Passkeys
  4. Go to Wallets and enable embedded wallets
  5. Go to Chains and enable the networks you want to support (EVM and/or Solana)
For testing, we recommend starting with Email OTP authentication and an EVM testnet like Base Sepolia. This gives you a complete setup without requiring real phone numbers or mainnet transactions.

Troubleshooting

Common Issues

  • Could not resolve: dynamic-sdk-android.aar
    • Make sure you’ve placed the AAR file in your app/libs directory
    • Try cleaning the build (Build → Clean Project)
    • Restart Android Studio
  • SDK not initialized
    • Ensure DynamicSDK.initialize() is called in your MainActivity.onCreate() before any views access the SDK
    • Don’t call DynamicSDK.getInstance() before initialization
  • Authentication callbacks not working
    • Verify your URL scheme is configured in AndroidManifest.xml
    • Ensure the redirectUrl matches your URL scheme
    • Whitelist your deep link URL in the Dynamic dashboard under Security → Whitelist Mobile Deeplink
  • Wallets not appearing after login
    • Wallets are created asynchronously after authentication
    • Use sdk.wallets.userWalletsChanges Flow to listen for wallet updates
    • Check that embedded wallets are enabled in your dashboard
  • DynamicUI overlay not showing
    • Make sure DynamicUI() composable is included in your app’s root layout
    • Verify it’s placed inside a Box or similar container that allows overlays

What’s Next

Great! Your SDK is now configured and ready to use. Here’s what you can do next:
  1. Authentication Guide - Learn how to implement user authentication with email, SMS, and passkeys
  2. Social Authentication - Add social login options like Google, Apple, and Farcaster
  3. Session Management - Manage authentication state with Kotlin Flow
  4. Wallet Operations - Work with balances and sign messages
  5. EVM Operations - Perform EVM transactions
  6. Solana Operations - Send Solana transactions
📱 Complete Example App: For a fully functional Android app demonstrating all SDK features, check out our Android SDK Example App. It includes authentication flows (email/SMS OTP, social login, passkeys), wallet management, EVM/Solana transactions, MFA, and passkey examples with Jetpack Compose.