Production example

The full ViewModel pattern: challenge fetch off the main thread, with loading and retry states.

The challenge fetch is a network call and must run off the main thread. ensureRegistered() is a suspend function: call it from viewModelScope.

ViewModel

class PosViewModel(private val app: Application) : AndroidViewModel(app) {

    private val terminalId           = "T0001"
    private val merchantId           = "00000001"
    private val paymentFacilitatorId = 1L

    private val _isInitializing = MutableStateFlow(false)
    val isInitializing: StateFlow<Boolean> = _isInitializing.asStateFlow()

    private val _initError = MutableStateFlow<String?>(null)
    val initError: StateFlow<String?> = _initError.asStateFlow()

    fun ensureRegisteredThenRun(onReady: () -> Unit) {
        viewModelScope.launch {
            _isInitializing.value = true
            _initError.value      = null

            val registered = BlincPosSdk.ensureRegistered(
                context              = app,
                terminalId           = terminalId,
                merchantId           = merchantId,
                paymentFacilitatorID = paymentFacilitatorId,
                environment          = Environment.TEST // Environment.LIVE for prod
                challengeProvider    = {
                    withContext(Dispatchers.IO) {
                        YourBackendApiClient.fetchChallenge()
                    }
                }
            )

            _isInitializing.value = false

            if (registered) {
                onReady()
            } else {
                _initError.value = "Could not register the terminal. Please try again."
            }
        }
    }

    fun clearError() { _initError.value = null }
}

Activity

class PosActivity : ComponentActivity() {

    private val viewModel: PosViewModel by viewModels()
    private var lastResult by mutableStateOf<TransactionResult?>(null)

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

        BlincPosSdk.registerLauncher(this) { result ->
            lastResult = result
        }

        setContent {
            val isInitializing by viewModel.isInitializing.collectAsState()
            val initError      by viewModel.initError.collectAsState()

            PosScreen(
                isInitializing = isInitializing,
                initError      = initError,
                lastResult     = lastResult,
                onClearError   = { viewModel.clearError() },
                onPayNfc = {
                    viewModel.ensureRegisteredThenRun {
                        BlincPosSdk.startTransactionWithNFC(
                            context  = this,
                            amount   = 199900L,
                            metadata = """{"orderId":"ORD-001"}"""
                        )
                    }
                },
                onPayQr = {
                    viewModel.ensureRegisteredThenRun {
                        BlincPosSdk.startTransactionWithQR(
                            context = this,
                            amount  = 199900L
                        )
                    }
                }
            )
        }
    }
}

Keep the challenge-fetch timeout short

Around 10 seconds, paired with a visible retry. A long timeout leaves the cashier staring at a spinner if connectivity drops mid-fetch.


Did this page help you?