1

In my Database i have a table called Account which looks kinda like this

@Entity(tableName = "accounts", primaryKeys = ["server_id", "account_id"])
data class Account(

    @ColumnInfo(name = "server_id")
    val serverId: Long,

    @ColumnInfo(name = "account_id")
    val accountId: Int,

    @ColumnInfo(name = "first_name", defaultValue = "")
    var firstname: String
)

So lets say that we have the following Database snapshot

server_id account_id first_name 1 10 Zak 1 11 Tom 1 12 Bob 1 13 Jim 1 14 Mike

Now i also have the following POJO which represents an available video room inside a chatRoom

data class RoomInfo(
    @SerializedName("m")
    val participantIntList: List<Int>,
    @SerializedName("o")
    val roomId: String,
    @SerializedName("s")
    val status: Int
)

So i get an incoming response from my Socket which is like the following

[
   {"m": [10, 11, 12], "o": "room_technical", "s": 1}, 
   {"m": [13, 14], "o": "room_operation", "s": 1}
]

which i map it in a List so i have

val roomInfo: LiveData<List<RoomInfo>> = socketManager.roomInfo

// So the value is basically the json converted to a list of RoomInfos using Gson

In order to display this available list of Rooms to the User i need to convert the m (which is the members that are inside the room right now) from accountIds to account.firstnames.

So what i want to have finally is a List of a new object called RoomInfoItem which will hold the list of the rooms with the accountIds converted to firstNames from the Account table of the Database.

data class RoomInfoItem(
    val roomInfo: RoomInfo,
    val participantNames: List<String>
)

So if we make the transformation we need to have the following result

RoomInfo (
      // RoomInfo 
      {"m": [10, 11, 12], "o": "room_technical", "s": 1}, 
      // Participant names
      ["Zak", "Tom", "Bob"]
   )

   RoomInfo (
      // RoomInfo 
       {"m": [13, 14], "o": "room_operation", "s": 1}, 
      // Participant names
      ["Jim", "Mike"]
   )

My Activity needs to observe a LiveData with the RoomInfoItems so what i want is given the LiveData<List> to transform it to LiveData<List>. How can i do that?

james04
  • 1,580
  • 2
  • 20
  • 46
  • Looks like you can use Transformations.switchMap : `getRoomInfoLiveData(). switchMap{ roomInfo -> getParticipantNamesLifeData(roomInfo. participantIntList).map{RoomInfoItem(roomInfo, it)}}` – Artem Viter Jun 13 '21 at 23:24
  • This can't happen because the viewModel holds a List. So i have to do that for each element of that list – james04 Jun 14 '21 at 07:53
  • I don't understand: do you want to make livedata with type LiveData> ? It means when some of RoomInfo will changed then you will call getParticipantNames for each item in list and you will observe changes of all data ... Or maybe when user do something u need observe data only for one RoomInfo object (RoomInfoItem) ? – Artem Viter Jun 14 '21 at 12:44
  • I have a LiveData>. In every roomInfo there is a List (accountIds). I want to Transform the LiveData> to LiveData>. So for every roomInfo in the initial List i need to access the Database. – james04 Jun 14 '21 at 15:51
  • Did you try to use my approach from answer ? – Artem Viter Jun 18 '21 at 18:23

2 Answers2

0

Well, finally i could not find a solution but i think that what i am trying to achieve, cannot be done using the Transformation.switchMap or Transformation.map

james04
  • 1,580
  • 2
  • 20
  • 46
  • Looks like its difficult to achive what you want by using just Transformation.switchMap or /and Transformation.map because when you receive new list of `RoomInfo` you want to transform their to List> and them combine all of them to LifeData>. I.e. you what observe RoomInfo and for each RoomInfo you what observe RoomInfoItem – Artem Viter Jun 16 '21 at 18:11
0

As I understand you want get LiveData<List<RoomInfoItem>> by analogy LiveData<List<ResultData>> in my sample. And you have next condition: you want to observe list of RoomInfo and for each RoomInfo in this list you want to observe participantNames. (Each pair of RoomInfo and participantNames you map to RoomInfoItem). I think you can achive this behaviour by using MediatorLiveData. I show sample how you can do this bellow:

// For example we have method which returns liveData of List<String> - analogy to your List<RoomInfo>
fun firstLifeData(): LiveData<List<String>> {
    //TODO
}

// and we have method which returns liveData of List<Int> - analogy to your participantNames(List<String>)
fun secondLifeData(param: String): LiveData<List<Int>> {
    //TODO
}

//and analogy of your RoomInfoItem
data class ResultData(
    val param: String,
    val additionalData: List<Int>
)

Then I will show my idea of implementation of combined liveDatas:

@MainThread
fun <T> combinedLiveData(liveDatas: List<LiveData<T>>): LiveData<List<T>> {
    val mediatorLiveData = MediatorLiveData<List<T>>()
    // cache for values which emit each liveData, where key is an index of liveData  from input [liveDatas] list
    val liveDataIndexToValue: MutableMap<Int, T> = HashMap()
    // when [countOfNotEmittedLifeDatas] is 0 then each liveData from [liveDatas] emited value
    var countOfNotEmittedLifeDatas = liveDatas.size
    liveDatas.forEachIndexed { index, liveData ->
        mediatorLiveData.addSource(liveData) { value ->
            // when liveData emits first value then mack it by decrementing of countOfNotEmittedLifeDatas
            if (!liveDataIndexToValue.containsKey(index)) {
                countOfNotEmittedLifeDatas--
            }
            liveDataIndexToValue[index] = value
            // when countOfNotEmittedLifeDatas is 0 then all liveDatas emits at least one value
            if (countOfNotEmittedLifeDatas == 0) {
                // then we can push list of values next to client-side observer
                mediatorLiveData.value = liveDataIndexToValue.toListWithoutSavingOrder()
            }
        }
    }
    return mediatorLiveData
}

fun <V> Map<Int, V>.toListWithoutSavingOrder(): List<V> = this.values.toList()

/**
 *  Key should be an order
 */
fun <V> Map<Int, V>.toListWithSavingOrder(): List<V> = this.entries.sortedBy { it.key }.map { it.value }
/*
or you can run [for] cycle by liveDataIndexToValue in [combinedLiveData] method or apply [mapIndexed] like:

    liveDatas.mapIndexed{ index, _ ->
        liveDataIndexToValue[index]
    }
to receive ordered list.
 */


And how to use all of that together:

fun resultSample(): LiveData<List<ResultData>> {
    return firstLifeData().switchMap { listOfParams ->
        val liveDatas = listOfParams.map { param -> secondLifeData(param).map { ResultData(param, it) } }
        combinedLiveData(liveDatas)
    }
}

// u can add extension function like:
fun <T> List<LiveData<T>>.combined(): LiveData<List<T>> = combinedLiveData(this)

// and then use it in this way
fun resultSample_2(): LiveData<List<ResultData>> = firstLifeData().switchMap { listOfParams ->
    listOfParams.map { param -> secondLifeData(param).map { ResultData(param, it) } }.combined()
}

I suggest you to consider using room's Relations. I think by room's Relations you can get LiveData<RoomInfoItem> . I cant get you more details about this approach because I don't know details about your data scheme and domain, at the moment.

Artem Viter
  • 804
  • 7
  • 12
  • Artem, i really appreciate your effort and time to deal with my issue, so i took some time to edit and explain a little better what i want to do, so you can be able to know if your solution is on my needs. Thank you – james04 Jun 18 '21 at 19:19
  • Can you please refactor your example according to mine. It seems confusing. I mean use my own objects... – james04 Jun 18 '21 at 19:49