How to collect a flow list value inside a relationship room database class and jetpack compose

449 Views Asked by At

In my Android app I want to show only items that are within a group that the user touchs on using jetpack compose, room, kotlin coroutines and flow and update screen when add a new item for that group. I can create groups and associate items for each group with one-to-many relationship room database. I can also show all items from all groups. But I don't know how to show only the items in the group that the user touchs on and update the screen by collecting flow.

Group's Screen:

Group's Screen

When I touch on group1, I want to show only group1 items and when I add a new item on this group update screen automaticaly:

Items' Screen

Some codes...

Groups Entity:

@Entity
data class Groups(
    @PrimaryKey(autoGenerate = true)
    val groupId: Int,
    val groupName: String,
    //more code
)

Groups Dao:

@Dao
interface GroupsDao {
    @Query("SELECT * FROM Groups ORDER BY groupId ASC")
    fun getGroups(): Flow<List<Groups>>

    @Transaction
    @Query("SELECT * FROM Groups")
    fun getGroupWithItens(): Flow<List<GroupWithItens>>

    @Query("SELECT * FROM Groups WHERE groupId = :groupId")
    suspend fun getGroup(groupId: Int): Groups

    //more code
}

Items Entity:

@Entity
data class Itens(
    @PrimaryKey(autoGenerate = true)
    val itemId: Int,
    val groupOwnerId: Int,
    val itemName: String,
    //more code
)

Items Dao:

@Dao
interface ItensDao {
    @Query("SELECT * FROM 'Itens' ORDER BY itemId ASC")
    fun getItens(): Flow<List<Itens>>

    @Query("SELECT * FROM 'Itens' WHERE itemId = :itemId")
    suspend fun getItem(itemId: Int): Itens

    //more code
}

Items ViewModel:

@HiltViewModel
class ItensViewModel @Inject constructor(
    private val repo: ItensRepository
) : ViewModel() {
    var item by mutableStateOf(Itens(0, 0, NO_VALUE, NO_VALUE))
        private set

    val itens = repo.getItens()

    fun getItem(itemId: Int) = viewModelScope.launch {
        item = repo.getItem(itemId)
    }

    //more code
}

Relationship data class GroupWithItens:

data class GroupWithItens(
    @Embedded val groups: Groups,
    @Relation(
        parentColumn = "groupId",
        entityColumn = "groupOwnerId"
    )
    val itens: List<Itens> // THIS LIST I WANT DO COLLECT
)

GroupWithItens Dao:

@Dao
interface GroupWithItensDao {
    @Transaction
    @Query("SELECT * FROM Groups WHERE groupId = :groupId")
    fun getGroupWithItens(groupId: Int): Flow<List<GroupWithItens>>
}

GroupWithItens Repository:

interface GroupWithItensRepository {
    fun getGroupWithItens(groupId: Int): Flow<List<GroupWithItens>>
}

GroupWithItens RepositoryImpl:

class GroupWithItensRepositoryImpl(
    private val groupWithItensDao: GroupWithItensDao
) : GroupWithItensRepository {
    override fun getGroupWithItens(groupId: Int) = groupWithItensDao.getGroupWithItens(groupId)
}

GroupWithItens ViewModel:

@HiltViewModel
class GroupWithItensViewModel @Inject constructor(
    private val repo: GroupWithItensRepository
) : ViewModel() {

    var groupWithItens: Flow<List<GroupWithItens>> = flow{GroupWithItens(Groups(0, "", "", LocalDateTime.now()), emptyList())}

    fun getGroupWithItens(groupId: Int) = viewModelScope.launch {
        groupWithItens = repo.getGroupWithItens(groupId)

        }
    }
}

ItensScreen:

@Composable
fun ItensScreen(
    viewModelItens: ItensViewModel = hiltViewModel(),
    viewModelGroups: GroupsViewModel = hiltViewModel(),
    viewModelGroupWithItens: GroupWithItensViewModel = hiltViewModel(),
    itemId: Int,
    groupId: Int,
    //more code
) {

    val itens by viewModelItens.itens.collectAsState(   //
        initial = emptyList()                           // THIS WORKS
    )                                                   //

    val groupWithItens by viewModelGroupWithItens.groupWithItens.collectAsState( //
        initial = 0                                                              // HERE I DON'T KNOW HOW TO GET JUST LIST<ITENS>
    )                                                                            //

    LaunchedEffect(Unit) {
        viewModelItens.getItem(itemId)
        viewModelGroups.getGroup(groupId)
        viewModelGroupWithItens.getGroupWithItens(groupId)
    }

    Log.d(
        "*****", "groupId: " + groupId
                + "\n" + "itens: " + itens
                + "\n" + "groupWithItens: " + groupWithItens
    )

    Scaffold(
        topBar = {
            //more code
        },
        bottomBar = {
           //more code
        },

        content = { padding ->
            ItensContent(
                padding = padding,
                itens = itens, //<- HERE SHOW ALL ITEMS
                deleteItem = { item ->
                    viewModelItens.deleteItem(item)
                }

         //more code
    )
}

Log output:

groupId: 1
itens: [Itens(itemId=1, groupOwnerId=1, itemName=item1_group1, itemDescription=), Itens(itemId=2, groupOwnerId=1, itemName=item2_group1, itemDescription=), Itens(itemId=4, groupOwnerId=2, itemName=item1_group2, itemDescription=)]
groupWithItens: [GroupWithItens(groups=Groups(groupId=1, groupName=group1, groupDescription=, groupDateCreation=2023-09-22T19:15:41.702022), itens=[Itens(itemId=1, groupOwnerId=1, itemName=item1_group1, itemDescription=), Itens(itemId=2, groupOwnerId=1, itemName=item2_group1, itemDescription=)])]

I want to collect just [Itens(itemId=1, groupOwnerId=1, itemName=item1_group1, itemDescription=), Itens(itemId=2, groupOwnerId=1, itemName=item2_group1, itemDescription=)])] part.

And composable cards:

@Composable
fun ItensContent(
    padding: PaddingValues,
    itens: List<Itens>,
    deleteItem: (item: Itens) -> Unit,
) {
    LazyColumn(
        //more code
    ) {
        items(
            items = itens
        ) { item ->
            ItemCard(
                item = item,
                deleteItem = {
                    deleteItem(item)
                }
            )
        }
    }
}

@Composable
fun ItemCard(
    item: Itens,
    deleteItem: () -> Unit,
) {
    Card( 
        // more code
    ) {
        //more code
    }
}

So, how can I collect List in GroupWithItens data class with Kotlin Flow? I try things like: groupWithItens.itens or val itens by viewModelGroupWithItens.groupWithItens.collect( itens-> itens = itens ) but no success. I appreciate for your help!!

1

There are 1 best solutions below

1
On

By default, as you have found, @Realation retrieves ALL children. So you have to override this practice.

To override this practice you would need either a query that trims the list of Itens (via an IN clause something like SELECT * FROM itens WHERE itenId IN (csv_of_ids)) OR to trim the list programatically.

In your case it is probably easier to do the latter (rather than build as CSV for the IN clause).

You will therefore need a function that can take the two parameters. The groupId and also the list of iten Ids. You can then first get the full groupWithItens and then create a a list of the wanted itens and finally return a new GroupWithItens build from the group and the trimmed list.

So first a query to suit getting the single Groups:-

@Query("SELECT * FROM Groups WHERE groupId=:groupId")
fun getSingleGroupWithItens(groupId: Int): GroupWithItens
  • No need to return a List when you know that you will only get 1 Groups as the groupId can only refer to 1 Groups.

Then a function e.g.

@Transaction
@Query("")
fun getGroupWithItensFromList(groupId: Int,itensIdLIst: List<Int>): GroupWithItens {
    val gwi = getSingleGroupWithItens(groupId = groupId)
    val ilist = arrayListOf<Itens>()
    for (i in gwi.itens) {
        for (id in itensIdLIst) {
            if (i.itemId == id) {
                ilist.add(i)
            }
        }
    }
    return GroupWithItens(groups = gwi.groups,ilist)
}
  • So the full GroupWithItens is retrieved, then
  • the trimmed list is extracted, and
  • a GroupWithItens is returned with the trimmed list of itens.

*Demo

Using:-

    db = TheDatabase.getInstance(this)
    daoGroups = db.getGroupsDao()
    daoItens = db.getItensDao()

    val g1Id = (daoGroups.insert(Groups(0,"GRP001"))).toInt()
    val g2Id = (daoGroups.insert(Groups(0,"GRP002"))).toInt()
    val g3Id = (daoGroups.insert(Groups(0,"GRP003"))).toInt()

    val i1Id = (daoItens.insert(Itens(0,g1Id,"I001"))).toInt()
    val i2Id = (daoItens.insert(Itens(0,g1Id,"I002"))).toInt()
    val i3Id = (daoItens.insert(Itens(0,g1Id,"I003"))).toInt()
    val i4Id = (daoItens.insert(Itens(0,g1Id,"I004"))).toInt()
    val i5Id = (daoItens.insert(Itens(0,g2Id,"I005"))).toInt()
    val i6Id = (daoItens.insert(Itens(0,g3Id,"I006"))).toInt()
    val i7Id = (daoItens.insert(Itens(0,g3Id,"I007"))).toInt()
    val i8Id = (daoItens.insert(Itens(0,g1Id,"I008"))).toInt()

    val ilist1 = listOf(i2Id,i8Id)
    val ilist2 = listOf(i5Id)
    val ilist3 = listOf(i1Id,i3Id,i4Id)

    logGroupWithItens(daoGroups.getGroupWithItensFromList(g1Id,ilist1),"TEST01")
    logGroupWithItens(daoGroups.getGroupWithItensFromList(g2Id,ilist2),"TEST02")
    logGroupWithItens(daoGroups.getGroupWithItensFromList(g1Id,ilist3),"TEST03")


}

fun logGroupWithItens(gwi: GroupWithItens, tagSuffix: String) {
    val sb = StringBuilder()
    for (i in gwi.itens) {
        sb.append("\n\tNAME is ${i.itemName} ID is ${i.itemId} OWNERGROUP ID = ${i.groupOwnerId}")
    }
    Log.d("DBINFO_${tagSuffix}","GROUP NAME IS ${gwi.groups.groupName} ID is ${gwi.groups.groupId}. " +
            "The GROUP has ${gwi.itens.size} SELECTED ITENS. They are:-${sb}")
}

The Result output to the log includes:-

D/DBINFO_TEST01: GROUP NAME IS GRP001 ID is 1. The GROUP has 2 SELECTED ITENS. They are:-
        NAME is I002 ID is 2 OWNERGROUP ID = 1
        NAME is I008 ID is 8 OWNERGROUP ID = 1
  • as requested only itens with id's 2 and 8 are retrieved (1 and 3 were not requested so have been trimmed)

:-

D/DBINFO_TEST02: GROUP NAME IS GRP002 ID is 2. The GROUP has 1 SELECTED ITENS. They are:-
        NAME is I005 ID is 5 OWNERGROUP ID = 2

:-

D/DBINFO_TEST03: GROUP NAME IS GRP001 ID is 1. The GROUP has 3 SELECTED ITENS. They are:-
        NAME is I001 ID is 1 OWNERGROUP ID = 1
        NAME is I003 ID is 3 OWNERGROUP ID = 1
        NAME is I004 ID is 4 OWNERGROUP ID = 1
  • (8 has been trimmed)