fix(cashu): init the wallet VM synchronously in composition body

Stack trace from runtime:

  java.lang.NullPointerException
    at CashuWalletViewModel.getState(CashuWalletViewModel.kt:139)
    at CashuWalletViewModel.getWalletEvent(CashuWalletViewModel.kt:142)
    at WalletScreen.kt:94

Cause: `CashuWalletViewModel.state` dereferences `account!!`, which is
populated by init(). I had init() inside `LaunchedEffect(Unit)` in the
three call sites — that effect only fires *after* the first composition
returns, so the very first read of `viewModel.walletEvent` (line 94 of
WalletScreen) hit a null account and threw.

The existing `WalletViewModel` (NWC) handles this by calling init()
directly in the composable body — init() is idempotent (just assigns
two fields), so recomposing is fine. Match that pattern in
WalletScreen, CashuWalletScreen, and AddCashuWalletScreen.

Both flavors compile clean; 24/24 NIP-60 jvm tests still pass.

https://claude.ai/code/session_01MdWddiar819f8XYt5N8BjP
This commit is contained in:
Claude
2026-05-27 15:17:41 +00:00
parent ba46f31359
commit 811aa26d57
3 changed files with 9 additions and 3 deletions
@@ -73,7 +73,9 @@ fun AddCashuWalletScreen(
nav: INav,
) {
val viewModel: CashuWalletViewModel = viewModel()
LaunchedEffect(Unit) { viewModel.init(accountViewModel) }
// Synchronous init so state-flow getters don't hit a null `account` on
// the first composition pass. init() is idempotent — just sets refs.
viewModel.init(accountViewModel)
val existingWallet by viewModel.walletEvent.collectAsState()
val existingMints by viewModel.mints.collectAsState()
@@ -91,7 +91,9 @@ fun CashuWalletScreen(
nav: INav,
) {
val viewModel: CashuWalletViewModel = viewModel()
LaunchedEffect(Unit) { viewModel.init(accountViewModel) }
// Synchronous init so state-flow getters don't hit a null `account` on
// the first composition pass. init() is idempotent — just sets refs.
viewModel.init(accountViewModel)
val walletEvent by viewModel.walletEvent.collectAsState()
val mints by viewModel.mints.collectAsState()
@@ -92,7 +92,9 @@ fun WalletScreen(
walletViewModel.init(accountViewModel)
val cashuWalletViewModel: CashuWalletViewModel = viewModel()
LaunchedEffect(Unit) { cashuWalletViewModel.init(accountViewModel) }
// Synchronous init so state-flow getters don't hit a null `account` on
// the first composition pass. init() is idempotent — just sets refs.
cashuWalletViewModel.init(accountViewModel)
val hasNwcWallet by walletViewModel.hasWalletSetup.collectAsState()
val cashuWalletEvent by cashuWalletViewModel.walletEvent.collectAsState()