Skip to content

Compose Frontend

mero-compose is an optional Jetpack Compose layer that ships alongside the core SDK — a MeroClient view-model exposing a StateFlow<MeroAuthState>, a MeroProvider/useMero pair for passing it down the tree, and ready-made LoginSheet / ConnectButton composables. It’s the analog of mero-react’s MeroProvider/useMero + LoginModal.

Add the optional dependency (see install):

implementation("com.calimero.mero:mero-compose:0.1.0")

Build a MeroClient, provide it with MeroProvider, then read it anywhere below with useMero():

import com.calimero.mero.compose.*
@Composable
fun App(context: Context) {
// Production factory: EncryptedPrefs token store + cross-process refresh lock
val client = remember { MeroClient.create(context, baseUrl = "https://your-node.example") }
MeroProvider(client) {
val c = useMero() // the client, anywhere in the tree
val state by c.state.collectAsStateWithLifecycle()
if (state.isAuthenticated) HomeScreen() else LoginSheet()
}
}
MeroClientStateFlow<MeroAuthState> · isAuthenticatedMeroProvider → useMero()collectAsStateWithLifecycle()CompositionLocalfalseLoginSheet()node URL · username · password→ client.login(…)trueyour Home screensession info · run RPCConnectButton → logout()

A plain (non-composable) class wrapping a Mero, exposing one observable state flow. MeroAuthState carries isAuthenticated, isLoading, nodeUrl, applicationId, contextId, contextIdentity, and error.

class MeroClient(
val mero: Mero,
baseUrl: String,
allowedNodeUrls: List<String> = emptyList(),
) {
val state: StateFlow<MeroAuthState>
val rpc: RpcClient // passthrough to mero.rpc
suspend fun login(username: String, password: String, bootstrapSecret: String? = null)
fun startSsoLogin(context: Context, options: AuthLoginOptions) // opens a Custom Tab
fun handleAuthCallback(url: String): Boolean // parse + adopt SSO tokens
suspend fun logout(clientId: String? = null)
companion object {
// Production factory: EncryptedPrefs store + cross-process refresh lock.
fun create(context: Context, baseUrl: String, allowedNodeUrls: List<String> = emptyList()): MeroClient
}
}

handleAuthCallback trust-checks the callback’s node_url against baseUrl / allowedNodeUrls before adopting the tokens, so a malicious deep link can’t point you at another node. Construct MeroClient(mero, baseUrl) directly with a pre-built Mero (e.g. a MemoryTokenStore) in tests.

Both default their client to useMero(), so inside a MeroProvider you can drop them in with no arguments:

Composable Purpose
MeroProvider(client) { … } provides the MeroClient via a CompositionLocal
useMero() reads the provided client (throws if no provider above)
LoginSheet(showBootstrapSecret, onAuthenticated) credential form → client.login(...)
ConnectButton(clientId, onConnect) “Connect” when signed out, “Log out” when signed in
LoginSheet(
showBootstrapSecret = true, // shows the first-login setup-code field
onAuthenticated = { /* navigate home */ },
)
ConnectButton(onConnect = { /* open your login sheet */ })
val client = useMero()
// kick off hosted login (opens a Custom Tab)
client.startSsoLogin(context, AuthLoginOptions(callbackUrl = "myapp://auth-callback", mode = "login"))
// in the deep-link Activity that receives myapp://auth-callback:
val ok = client.handleAuthCallback(intent.dataString ?: "")