Quickstart
This guide walks through setting up your Application class, MainActivity, Screen Providers, and implementing your first screen.
Step 1: Application Class
Create your Application class to initialize the environment and Koin dependency injection:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Configure the environment
EnvironmentConfig.initialize(
EnvironmentConfig(
environment = MsaEnvironment.Staging, // Use MsaEnvironment.Production for release
licenseFileName = "your-license-file.license"
)
)
// Initialize Koin with your screen providers
initKoin {
androidLogger()
androidContext(this@MyApplication)
modules(
module {
single<ScreenProviders> { providers }
}
)
}
}
}Step 2: Screen Providers
Create a ScreenProviders implementation that maps each screen to your Composable UI. This is the bridge between MSA Core and your app:
val providers = object : ScreenProviders {
override val statusBarColor: @Composable (AppScreen) -> Color = { screenType ->
when (screenType) {
LandingScreen -> MsaTheme.colors.mutedForeground
LoginScreen -> MsaTheme.colors.background
HomeScreen -> MsaTheme.colors.accent
SaleScreen, PreAuthScreen, CardRefundScreen -> MsaTheme.colors.primary
else -> MsaTheme.colors.background
}
}
// Core
override val loginScreen = ComposableScreen { s, a -> LoginScreenUI(s, a) }
override val landingScreen = ComposableScreen { s, a -> LandingScreenUI(s, a) }
override val homeScreen = ComposableScreen { s, a -> HomeScreenUI(s, a) }
// Transactions
override val historyScreen = ComposableScreen { s, a -> HistoryScreen(s, a) }
override val trxDetailsScreen = ComposableScreen { s, a -> TransactionDetailsScreen(s, a) }
override val trxResultScreen = ComposableScreen { s, a -> TransactionResultScreen(s, a) }
override val trxReceiptScreen = ComposableScreen { s, a -> ReceiptUIScreen(s, a) }
// Payments
override val saleScreen = ComposableScreen { s, a -> SaleScreen(s, a) }
override val preAuthScreen = ComposableScreen { s, a -> PreAuthScreen(s, a) }
override val cardRefundScreen = ComposableScreen { s, a -> CardRefundScreen(s, a) }
override val paymentSelectionScreen = ComposableScreen { s, a -> PaymentSelectionScreen(s, a) }
override val paymentQRcodeSelectionScreen = ComposableScreen { s, a -> PaymentQRSelectionScreen(s, a) }
// Settlement
override val settlementScreen = ComposableScreen { s, a -> SettlementBatchScreen(s, a) }
override val settlementDetailsScreen = ComposableScreen { s, a -> SettlementBatchDetailsScreen(s, a) }
override val settlementResultScreen = ComposableScreen { s, a -> SettlementBatchResultScreen(s, a) }
// Settings
override val settingsScreen = ComposableScreen { s, a -> MainSettingScreen(s, a) }
override val shopInfoScreen = ComposableScreen { s, a -> ShopInfoUIScreen(s, a) }
// App-to-App
override val activationApp2AppScreen = ComposableScreen { s, a -> App2AppActivationScreen(s, a) }
override val saleApp2AppScreen = ComposableScreen { s, a -> App2AppSaleScreen(s, a) }
override val queryApp2AppScreen = ComposableScreen { s, a -> App2AppQueryScreen(s, a) }
override val settlementApp2AppScreen = ComposableScreen { s, a -> App2AppSettlementScreen(s, a) }
override val splashApp2AppScreen = ComposableScreen { s, a -> App2AppSplashScreen(s, a) }
}Step 3: MainActivity
Your main activity extends BaseMsaActivity which handles SDK lifecycle and dependency injection:
class MainActivity : BaseMsaActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App(screenProviders = providers)
}
}
override fun provideAdditionalKoinModules(): List<Module> {
return listOf(
provideNFCManager()
)
}
}| Component | Purpose |
|---|---|
BaseMsaActivity | Manages SDK lifecycle, DI setup, and core functionality |
App() | Entry-point Composable that handles navigation and state distribution |
provideNFCManager() | Provides NFC card reading capability |
Step 4: Implement a Screen — Login Example
Each screen receives a State object with data to display and a triggerAction callback to send user events back to MSA Core. Here's a real-world Login screen implementation:
@Composable
fun LoginScreenUI(
state: LoginState,
triggerAction: (LoginAction) -> Unit
) = MsaUiStateHandler(isLoading = state.isLoading, state.deviceErrorState) {
var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }
val isButtonEnabled = textFieldValue.text.filter { it.isDigit() }.length == 12
// Format passcode as XXXX-XXXX-XXXX
fun formatWithHyphens(text: String): String {
val digitsOnly = text.filter { it.isDigit() }.take(12)
return buildString {
digitsOnly.forEachIndexed { index, char ->
append(char)
if ((index + 1) % 4 == 0 && index != digitsOnly.lastIndex) append('-')
}
}
}
Scaffold(
modifier = Modifier
.background(MsaTheme.colors.background)
.statusBarsPadding()
.navigationBarsPadding(),
bottomBar = {
Column(
modifier = Modifier
.background(MsaTheme.colors.background)
.fillMaxWidth()
.padding(horizontal = MsaTheme.spacing.md)
.padding(bottom = MsaTheme.spacing.md)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(MsaTheme.spacing.md),
verticalAlignment = Alignment.CenterVertically
) {
// Login button
MSAButton(
text = "Login",
enabled = isButtonEnabled,
onClick = {
triggerAction(
LoginAction.LoginClicked(
textFieldValue.text.replace("-", "")
)
)
},
modifier = Modifier.weight(1f)
)
// Biometric button (shown if supported and enabled)
if (state.isBiometricSupported && state.isBiometricEnabled) {
Box(
modifier = Modifier
.size(MsaTheme.minTouchSize.lg)
.clip(MsaTheme.shapes.large)
.background(MsaTheme.colors.primary)
.clickable { triggerAction(LoginAction.BioMetricClicked) },
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.fingerprint_icon_button),
contentDescription = "Biometric Login",
modifier = Modifier.size(MsaTheme.iconSize.xl)
)
}
}
}
}
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.background(MsaTheme.colors.background)
.verticalScroll(rememberScrollState())
.padding(horizontal = MsaTheme.spacing.md)
) {
Text(
text = "Passcode Login",
color = MsaTheme.colors.foreground,
style = MsaTheme.typography.headlineSmall,
)
Spacer(Modifier.height(MsaTheme.spacing.xl))
// Passcode input with XXXX-XXXX-XXXX formatting
OutlinedTextField(
value = textFieldValue,
onValueChange = { newValue ->
val formatted = formatWithHyphens(newValue.text)
textFieldValue = TextFieldValue(
text = formatted,
selection = TextRange(formatted.length)
)
},
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("XXXX-XXXX-XXXX") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true
)
// Error display
state.errorState?.exception?.errorMessage?.let {
Text(
text = it,
style = MsaTheme.typography.labelMedium,
color = MsaTheme.colors.error
)
}
}
}
}Key Patterns in This Example
| Pattern | Details |
|---|---|
| State reading | state.isLoading, state.isBiometricSupported, state.errorState |
| Actions | LoginAction.LoginClicked(passcode), LoginAction.BioMetricClicked |
| MsaUiStateHandler | Wraps your UI to handle loading overlays and device errors |
| MsaTheme | Access SDK theme colors, spacing, typography, and shapes |
Next Steps
- Theming & Branding — Customize colors, typography, shapes, and images
- Screen Providers Reference — Complete list of all screens with their State and Action classes
- Example project (opens in a new tab) — Full working implementation