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 in its Cloud mode: sign-in is
“Continue with Calimero” and nothing else — no node URL, username or password.
See Authentication
for what happens underneath.
Add the optional dependency (see install):
implementation("com.calimero.mero:mero-compose:0.1.0")Wire it up
Section titled “Wire it up”Build a MeroClient, provide it with MeroProvider, then read it anywhere below
with useMero():
import com.calimero.mero.compose.*
@Composablefun App(context: Context) { // Production factory: device keys + session in EncryptedSharedPreferences val client = remember { MeroClient.create(context) } LaunchedEffect(Unit) { client.restore() } // reconnect a saved session
MeroProvider(client) { val c = useMero() // the client, anywhere in the tree val state by c.state.collectAsStateWithLifecycle() if (state.isAuthenticated) HomeScreen() else LoginSheet(callbackUrl = "myapp://calimero-enrol") }}
// The Activity that owns the callback intent filter forwards the wallet's redirect:override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) intent.data?.toString()?.let { lifecycleScope.launch { client.handleEnrolmentCallback(it) } }}MeroClient
Section titled “MeroClient”A plain (non-composable) class wrapping a CloudAccount, exposing one observable
state flow. MeroAuthState carries isAuthenticated, isLoading, account,
device, relayUrl, sessionReady (a Bearer session on the relay exists),
relayNote, cancelled and error, plus signedInWithoutRelay.
class MeroClient( val account: CloudAccount, tokenStore: TokenStore = MemoryTokenStore(), launchWallet: (Context, String) -> Unit = WalletLauncher::launch, // a Custom Tab) { val state: StateFlow<MeroAuthState> val relay: RelayClient? // warranted writes, reads by query val mero: Mero? // admin / events on the relay (Bearer)
fun signInWithCloud(context: Context, callbackUrl: String) suspend fun handleEnrolmentCallback(url: String): Boolean suspend fun restore() suspend fun joinWithInvitation(namespaceId: String, invitation: SignedGroupOpenInvitation): JoinResult suspend fun signOut(forgetDevice: Boolean = false)
companion object { fun create(context: Context, cloudBaseUrl: String = …, walletUrl: String = …): MeroClient }}handleEnrolmentCallback verifies the certificate against this device’s own keys
and the state it sent before storing anything, so a crafted deep link cannot sign
the app in to someone else’s account. A declined approval sets cancelled, not
error.
Drop-in composables
Section titled “Drop-in composables”Both default their client to useMero():
| Composable | Purpose |
|---|---|
MeroProvider(client) { … } |
provides the MeroClient via a CompositionLocal |
useMero() |
reads the provided client (throws if no provider above) |
LoginSheet(callbackUrl, onAuthenticated, colors) |
“Continue with Calimero” → client.signInWithCloud(...) |
ConnectButton(callbackUrl, onConnect) |
“Continue with Calimero” when signed out, “Sign out” when signed in |
LoginSheet( callbackUrl = "myapp://calimero-enrol", onAuthenticated = { /* navigate home */ },)
ConnectButton(callbackUrl = "myapp://calimero-enrol")LoginSheet uses the light Calimero tokens by default; pass LoginSheetColors(...)
to restyle it.