---
updatedAt: 2026-11-11T10:49:16.000Z
agentTools:
  siteIndex: https://developer.paymentology.com/llms.txt
  projectIndex: https://developer.paymentology.com/lume/llms.txt
---

# Error Handling

# Android SDK: Error Handling

Best practices and patterns for handling errors in the Digital Card Engine SDK, including network failures, authentication issues, and MCD-specific errors.

## Error Types

### 1. CardProviderException

Custom SDK exception for API and authentication errors.

```kotlin
sealed class CardProviderException(message: String) : Exception(message) {
    class AuthenticationError(message: String) : CardProviderException(message)
    class NetworkError(message: String) : CardProviderException(message)
    class ValidationError(message: String) : CardProviderException(message)
    class ServerError(message: String) : CardProviderException(message)
}
```

### 2. MCD Errors

Errors from the MeaWallet MCD SDK when fetching secure card data.

```kotlin
// MCD onFailure callback
override fun onFailure(mcdError: McdError) {
    // mcdError.code: Error code
    // mcdError.name: Error type name
    // mcdError.message: Description
    // mcdError.requestId: Request identifier for debugging
}
```

### 3. Standard Exceptions

* `IllegalStateException` - SDK not initialized, invalid state
* `IllegalArgumentException` - Invalid parameters
* `IOException` - Network connectivity issues
* `SecurityException` - Permission issues

***

## Common Error Scenarios

### Authentication Errors

**Cause**: Invalid, expired, or missing authentication token.

**Symptoms**:

* 401 Unauthorized responses
* "Auth token cannot be empty or blank"
* "Auth token appears to be too short"

**Solution**:

```kotlin
import com.paymentology.ei_card_provider_sdk.EiCardProviderSDK

class TokenManager(private val sdk: DigitalCardEngineSDK) {
    
    suspend fun refreshTokenIfNeeded() {
        try {
            val cards = sdk.getCards()
            // Success - token valid
        } catch (e: CardProviderException.AuthenticationError) {
            // Token expired - fetch new one
            val newToken = fetchFreshTokenFromBackend()
            EiCardProviderSDK.updateAuthToken(newToken)
            
            // Retry operation
            val cards = sdk.getCards()
        }
    }
    
    private suspend fun fetchFreshTokenFromBackend(): String {
        // Your backend call to get new token
        return myBackendApi.refreshToken()
    }
}
```

### Network Errors

**Cause**: No internet connection, timeout, DNS failure.

**Symptoms**:

* `SocketTimeoutException`
* `UnknownHostException`
* `ConnectException`

**Solution**:

```kotlin
import androidx.compose.runtime.*
import kotlinx.coroutines.delay

@Composable
fun CardListWithRetry(sdk: DigitalCardEngineSDK) {
    var retryCount by remember { mutableStateOf(0) }
    var errorMessage by remember { mutableStateOf<String?>(null) }
    
    LaunchedEffect(retryCount) {
        try {
            val cards = sdk.getCards()
            errorMessage = null
        } catch (e: IOException) {
            errorMessage = "Network error. Please check your connection."
        } catch (e: Exception) {
            errorMessage = "Error: ${e.message}"
        }
    }
    
    if (errorMessage != null) {
        ErrorView(
            message = errorMessage!!,
            onRetry = { retryCount++ }
        )
    } else {
        UILibrary.CardListView(sdk = sdk, /* ... */)
    }
}
```

### MCD Errors (Secure Card Data)

**Cause**: Missing TOTP secret, card not selected, MCD configuration issues.

**Symptoms**:

* MCD error codes in logs
* "No selected card" errors
* Secure fields not displaying

**Common MCD Error Codes**:

| Code   | Description      | Solution                               |
| ------ | ---------------- | -------------------------------------- |
| `1001` | Invalid card ID  | Verify `giftCardId` is correct         |
| `1002` | Invalid secret   | Check TOTP secret generation           |
| `1003` | Card not found   | Ensure card exists and user has access |
| `2001` | Network error    | Check connectivity, retry              |
| `3001` | Invalid response | Contact support                        |

**Solution**:

```kotlin
import android.util.Log

lifecycleScope.launch {
    try {
        // Ensure card is selected
        sdk.selectCard(card)
        
        // Fetch secure card images
        val images = sdk.getCardImages()
        
        if (images != null) {
            panImageView.setImageBitmap(images["pan"])
            cvvImageView.setImageBitmap(images["cvv"])
        }
        
    } catch (e: IllegalStateException) {
        when {
            e.message?.contains("No selected card") == true -> {
                Log.e("MCD", "Card not selected - call sdk.selectCard() first")
                showError("Card selection error")
            }
            e.message?.contains("MCD error") == true -> {
                Log.e("MCD", "MCD SDK error: ${e.message}")
                showError("Unable to load secure card details")
            }
            else -> {
                Log.e("MCD", "Unexpected error: ${e.message}")
                showError("An error occurred")
            }
        }
    }
}
```

### Initialization Errors

**Cause**: SDK not initialized before use.

**Symptoms**:

* "SDK not initialized" errors
* Null pointer exceptions
* Crashes on API calls

**Solution**:

```kotlin
class MyApp : Application() {
    lateinit var sdk: DigitalCardEngineSDK
    private var isInitialized = false
    
    override fun onCreate() {
        super.onCreate()
        initializeSDK()
    }
    
    private fun initializeSDK() {
        try {
            sdk = DigitalCardEngineSDK()
            
            val config = DceConfiguration(/* ... */)
            
            sdk.initialize(
                context = this,
                env = Environment.TEST,
                dceConfiguration = config
            )
            
            isInitialized = true
            
        } catch (e: Exception) {
            Log.e("App", "Failed to initialize SDK: ${e.message}")
            // Handle critical error - maybe show error screen
        }
    }
    
    fun getSdk(): DigitalCardEngineSDK {
        if (!isInitialized) {
            throw IllegalStateException("SDK not initialized")
        }
        return sdk
    }
}
```

***

## Error Handling Patterns

### 1. Try-Catch Pattern

Use for synchronous operations or within coroutines.

```kotlin
import kotlinx.coroutines.launch

lifecycleScope.launch {
    try {
        val cards = sdk.getCards()
        updateUI(cards)
    } catch (e: CardProviderException.AuthenticationError) {
        handleAuthError()
    } catch (e: CardProviderException.NetworkError) {
        showRetryOption()
    } catch (e: Exception) {
        logError(e)
        showGenericError()
    }
}
```

### 2. Result Pattern

Use for cleaner error propagation.

```kotlin
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

suspend fun fetchCardsResult(): Result<List<CardEntity>> {
    return try {
        val cards = sdk.getCards()
        Result.Success(cards)
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// Usage
when (val result = fetchCardsResult()) {
    is Result.Success -> updateUI(result.data)
    is Result.Error -> handleError(result.exception)
}
```

### 3. State Pattern (Compose)

Use for UI state management.

```kotlin
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

class CardListViewModel(private val sdk: DigitalCardEngineSDK) : ViewModel() {
    
    var uiState by mutableStateOf<UiState<List<CardEntity>>>(UiState.Loading)
        private set
    
    fun loadCards() {
        uiState = UiState.Loading
        
        viewModelScope.launch {
            uiState = try {
                val cards = sdk.getCards()
                UiState.Success(cards)
            } catch (e: Exception) {
                UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

// Usage in Compose
@Composable
fun CardListScreen(viewModel: CardListViewModel) {
    when (val state = viewModel.uiState) {
        is UiState.Loading -> LoadingIndicator()
        is UiState.Success -> CardList(state.data)
        is UiState.Error -> ErrorView(state.message) {
            viewModel.loadCards()
        }
    }
}
```

***

## Retry Strategies

### Exponential Backoff

```kotlin
import kotlinx.coroutines.delay
import kotlin.math.pow

suspend fun <T> retryWithBackoff(
    maxRetries: Int = 3,
    initialDelayMs: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelayMs
    var lastException: Exception? = null
    
    repeat(maxRetries) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            lastException = e
            
            if (attempt < maxRetries - 1) {
                Log.w("Retry", "Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms")
                delay(currentDelay)
                currentDelay = (currentDelay * factor).toLong()
            }
        }
    }
    
    throw lastException ?: Exception("Max retries exceeded")
}

// Usage
lifecycleScope.launch {
    try {
        val cards = retryWithBackoff {
            sdk.getCards()
        }
        updateUI(cards)
    } catch (e: Exception) {
        showError("Failed after multiple retries: ${e.message}")
    }
}
```

### Conditional Retry

```kotlin
fun shouldRetry(exception: Exception): Boolean {
    return when (exception) {
        is IOException -> true  // Network errors
        is CardProviderException.NetworkError -> true
        is CardProviderException.AuthenticationError -> false  // Don't retry auth errors
        else -> false
    }
}

suspend fun <T> retryIfAppropriate(
    maxRetries: Int = 3,
    block: suspend () -> T
): T {
    var lastException: Exception? = null
    
    repeat(maxRetries) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            lastException = e
            
            if (!shouldRetry(e) || attempt >= maxRetries - 1) {
                throw e
            }
            
            delay(1000L * (attempt + 1))
        }
    }
    
    throw lastException ?: Exception("Max retries exceeded")
}
```

***

## Logging Best Practices

### Structured Logging

```kotlin
import android.util.Log

object SdkLogger {
    private const val TAG = "DigitalCardEngine"
    
    fun logApiCall(endpoint: String, params: Map<String, Any>? = null) {
        Log.d(TAG, "API Call: $endpoint ${params?.let { "with $it" } ?: ""}")
    }
    
    fun logApiSuccess(endpoint: String, duration: Long) {
        Log.d(TAG, "API Success: $endpoint (${duration}ms)")
    }
    
    fun logApiError(endpoint: String, error: Exception) {
        Log.e(TAG, "API Error: $endpoint - ${error.message}", error)
    }
    
    fun logMcdError(error: McdError) {
        Log.e(TAG, "MCD Error [${error.code}]: ${error.name} - ${error.message} (reqId: ${error.requestId})")
    }
}

// Usage
try {
    SdkLogger.logApiCall("getCards")
    val startTime = System.currentTimeMillis()
    
    val cards = sdk.getCards()
    
    val duration = System.currentTimeMillis() - startTime
    SdkLogger.logApiSuccess("getCards", duration)
    
} catch (e: Exception) {
    SdkLogger.logApiError("getCards", e)
    throw e
}
```

***

## User-Friendly Error Messages

```kotlin
fun getUser FriendlyMessage(exception: Exception): String {
    return when (exception) {
        is CardProviderException.AuthenticationError ->
            "Your session has expired. Please log in again."
        
        is CardProviderException.NetworkError ->
            "Unable to connect. Please check your internet connection and try again."
        
        is CardProviderException.ServerError ->
            "Our servers are experiencing issues. Please try again later."
        
        is IOException ->
            "Connection problem. Please check your network and retry."
        
        is IllegalStateException -> when {
            exception.message?.contains("No selected card") == true ->
                "Card selection error. Please try again."
            exception.message?.contains("MCD error") == true ->
                "Unable to display secure card information."
            else ->
                "An unexpected error occurred."
        }
        
        else ->
            "Something went wrong. Please try again."
    }
}

// Usage
@Composable
fun ErrorView(exception: Exception, onRetry: () -> Unit) {
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = getUserFriendlyMessage(exception),
            style = MaterialTheme.typography.bodyLarge
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onRetry) {
            Text("Try Again")
        }
    }
}
```

***

## Complete Example

```kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class CardViewModel(private val sdk: DigitalCardEngineSDK) : ViewModel() {
    
    var state by mutableStateOf<CardState>(CardState.Loading)
        private set
    
    sealed class CardState {
        object Loading : CardState()
        data class Success(val cards: List<CardEntity>) : CardState()
        data class Error(val message: String, val canRetry: Boolean) : CardState()
    }
    
    init {
        loadCards()
    }
    
    fun loadCards() {
        state = CardState.Loading
        
        viewModelScope.launch {
            try {
                val cards = retryWithBackoff(maxRetries = 2) {
                    sdk.getCards()
                }
                
                state = CardState.Success(cards)
                
            } catch (e: CardProviderException.AuthenticationError) {
                // Try to refresh token
                try {
                    val newToken = refreshToken()
                    EiCardProviderSDK.updateAuthToken(newToken)
                    loadCards() // Retry after token refresh
                } catch (tokenError: Exception) {
                    state = CardState.Error(
                        message = "Authentication failed. Please log in again.",
                        canRetry = false
                    )
                }
                
            } catch (e: IOException) {
                state = CardState.Error(
                    message = "Network error. Check your connection.",
                    canRetry = true
                )
                
            } catch (e: Exception) {
                Log.e("CardViewModel", "Unexpected error", e)
                state = CardState.Error(
                    message = "An error occurred: ${e.message}",
                    canRetry = true
                )
            }
        }
    }
    
    private suspend fun refreshToken(): String {
        // Your token refresh logic
        throw NotImplementedError("Implement token refresh")
    }
}

@Composable
fun CardScreen(viewModel: CardViewModel) {
    when (val state = viewModel.state) {
        is CardViewModel.CardState.Loading -> {
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }
        
        is CardViewModel.CardState.Success -> {
            CardList(cards = state.cards)
        }
        
        is CardViewModel.CardState.Error -> {
            ErrorView(
                message = state.message,
                canRetry = state.canRetry,
                onRetry = if (state.canRetry) {
                    { viewModel.loadCards() }
                } else null
            )
        }
    }
}
```

***

## Troubleshooting Checklist

**SDK Initialization Issues**:

* [ ] SDK initialized in Application.onCreate()
* [ ] Valid token provided
* [ ] Correct environment selected (DEV/TEST/PROD)
* [ ] Internet permission in manifest

**Authentication Errors**:

* [ ] Token is not expired
* [ ] Token format is correct
* [ ] Token refresh logic implemented
* [ ] Backend issuing valid tokens

**Network Errors**:

* [ ] Device has internet connectivity
* [ ] Firewall not blocking requests
* [ ] Correct API endpoints configured
* [ ] SSL certificates valid

**MCD Errors**:

* [ ] MCD artifact added to dependencies
* [ ] MCD credentials configured in gradle.properties
* [ ] Card selected before fetching images
* [ ] TOTP secret available and valid
* [ ] Correct MCD environment for your build

***

## See Also

* [Initialize](initialize-1.md) - Proper SDK initialization
* [Card List](card-list-1.md) - Handling errors in card lists
* [Card Details](card-details.md) - MCD error handling
* [Data Models](data-models.md) - Understanding SDK data structures

# Sibling pages

* [Component Reference](https://developer.paymentology.com/lume/docs/component-reference.md)
* [Styling & Theming](https://developer.paymentology.com/lume/docs/styling-theming.md)
* [Data Models](https://developer.paymentology.com/lume/docs/data-models.md)