Skip to content

To provide the notification of the changes in a query's relation result to the subscription observers #1244

Description

@leiniersalfran

Is there an existing issue?

  • I have searched existing issues
    There is no other opened issue related to this

Use case

I'm writing a non-custodial cryptocurrency wallet app for Android. I have the next structure:

  • Vaults (seed phrases)
    |-> Wallets (derived from seed phrases or imported private keys)

So, I modelled 2 entities:
1- VaultEntity

@Entity
data class VaultEntity(
    @Id
    var id: Long? = null,
    ...
) {
    @Backlink(to = "vault")
    lateinit var wallets: ToMany<WalletEntity>
}

2- WalletEntity

@Entity
data class WalletEntity(
    @Id
    var id: Long? = null,
    ...
) {
    lateinit var vault: ToOne<VaultEntity>
}

Then, I observe the table with the relation in the user interface getting the flow from a subscription to a query:

[UI]

@Composable
fun WalletsLayout() {
    val appContainer = LocalAppContainer.current
    val vaultsWithWallets by appContainer.vaultsViewModel.vaultsWithWalletsFlow.collectAsState()

     LazyColumn {
        itemsIndexed(vaultsWithWallets) { vaultIndex, vaultWithWallets ->
            ...

            vaultWithWallets.wallets.forEachIndexed { walletIndex, wallet ->
                ....
            }
        }
    }
}

[View model]

class VaultsViewModel(private val repository: VaultsRepository) : ViewModel() {
   // Here, I create a single point of monitorization that several observers in the UI can use. So, only one query().subscribe() for all
    val vaultsWithWalletsFlow = repository.vaultsWithWalletsFlow.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )
}

[Repository]

class VaultsRepository(boxStore: BoxStore) {
    private val dispatcher = boxStore.newCachedThreadPoolDispatcher()
    private val box = boxStore.boxFor(VaultEntity::class)

    // Instead of using .observer { data -> onUpdate(data) }, I use the conveniente convertion to Flow because it is more natural to the UI and is the way Android proposes to monitorize the tables using Room
    val vaultsWithWalletsFlow =
        box.query().order(VaultEntity_.name).eager(VaultEntity_.wallets).build()
            .subscribe().toFlow()

    suspend fun createSeedPhraseVault(
        name: String
    ) = try {
        val insertedId = withContext(dispatcher) {
            box.put(
                VaultEntity(
                    type = VaultType.SEED_PHRASE.ordinal,
                    name = name.toString(Charsets.UTF_8),
                )
            )
        }

        Result.success(insertedId)
    } catch (e: Exception) {
        Result.failure(e)
    }
}

class WalletsRepository(
    private val context: Context,
    boxStore: BoxStore
) {
    private val dispatcher = boxStore.newCachedThreadPoolDispatcher()
    private val box = boxStore.boxFor(WalletEntity::class)

    suspend fun deriveWallet(
        vault: VaultEntity,
        name: String
    ) = try {
        withContext(dispatcher) {
            vault.wallets.reset()
            vault.wallets.add(
                WalletEntity(
                    name = name
                )
            )
            vault.wallets.applyChangesToDb()
        }

        Result.success(true)
    } catch (e: Exception) {
        Result.failure(e)
    }
}

The issue resides in the values of the relation that doesn't get notified to the observers when I modified them (add a new wallet to a vault) in another UI screen, so when I return from the wallet screen creation to the vaults and wallets screen, i cannot see the recently created wallet in the list.

I imagine that this relation thing works similar to an SQL SELECT LEFT JOIN. I suppose that this should be the behavior, but it is not. As a note, Room does notify when the relation table changed.

Proposed solution

Implement the monitorization of the main table and all the relations. If I declare them and use them in the query is because they are important.

Alternatives

Maybe add a function that trigger the recomposition of the flow.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions