Using a coroutine inside a Transform.map function - android

Well here is a tricky one.
I have a query from my Room DB. The result is a Relational POJO
class MessageWithMsgQueueAccount {
#Embedded
var message: MessageDto? = null
#Relation(parentColumn = "clientMessageId", entityColumn = "clientMessageId", entity = MessageQueueDto::class)
var messageQueueList: List<MsgQueueAccount> = ArrayList()
}
So when i get that i apply a Transformation to this object so i can create another one that has only the information that i want.
fun toContactMessageChatItem(item: MessageWithMsgQueueAccount?, accountDto: AccountDto): MessageChatItem {
item?.message?.let {
// Procedure for creating the fields that i want
val isQuoted = it.quotemsgid > 0L
if (isQuoted) {
// Fetch quoted message body
}
return MessageChatItem(.....)
} ?: run {
return MessageChatItem(..........)
}
}
Since this is a chat, one field that i want to setup is a quoted message body. What i have as "input" is the messageId of the message that is being quoted. So that means that i have to make a query to my Room DB again inside the transform function.
The way i did it is this
val isQuoted = it.quotemsgid > 0L
var quotedBody = ""
if (isQuoted) {
// Fetch quoted message body
viewModelScope.launch(Dispatchers.IO) {
val quotedMessage = messagesRepository.getMessageByMessageId(it.quotemsgid)
withContext(Dispatchers.Main) {
quotedBody = quotedMessage.body
}
}
}
There no specific question but is there any better way to do something like this, meaning querying the DB inside a Transformation function. Is there a way that this would create a synchronize problem to my elements or something?

Related

Android room query is empty when using Flow

I am confused about using Flow with Room for database access. I want to be able to observe a table for changes but also access it directly.
However, when using a query that returns a Flow, the result always seems to be null although the table is not empty. A query that returns a List directly, seems to work.
Can someone explain the difference or show me which part of the documentation I might have missed?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db_button.setOnClickListener {
val user_dao = UserDatabase.getInstance(this).userDatabaseDao
lifecycleScope.launch {
user_dao.insertState(State(step=4))
val states = user_dao.getAllState().asLiveData().value
if (states == null || states.isEmpty()) {
println("null")
} else {
val s = states.first().step
println("step $s")
}
val direct = user_dao.getStatesDirect().first().step
println("direct step $direct")
}
}
}
}
#Entity(tableName = "state")
data class State(
#PrimaryKey(autoGenerate = true)
var id: Int = 0,
#ColumnInfo(name = "step")
var step: Int = 0
)
#Dao
interface UserDatabaseDao {
#Insert
suspend fun insertState(state: State)
#Query("SELECT * FROM state")
fun getAllState(): Flow<List<State>>
#Query("SELECT * FROM state")
suspend fun getStatesDirect(): List<State>
}
Output:
I/System.out: null
I/System.out: direct step 1
In Room, we use Flow or LiveData to observe changes in a query result. So Room queries the db asynchronously and when you are trying to retrieve the value immediately, it is highly probable to get null.
As a result, if you want to get the value immediately, you shouldn't use Flow as the return type of a room query function, just like what you did on getStatesDirect(): List<State>. On the other hand, if you want to observe data changes, you should use the collect terminal function on the Flow to receive its emissions:
lifecycleScope.launch {
user_dao.insertState(State(step=4))
val direct = user_dao.getStatesDirect().first().step
println("direct step $direct")
}
lifecycleScope.launch {
user_dao.getAllState().collect { states ->
if (states == null || states.isEmpty()) {
println("null")
} else {
val s = states.first().step
println("step $s")
}
}
}

Firestore and Unicode

The documentation describes that Firestore supports Unicode. You just need to insert already formatted text into Firestore. But when unloading, the following are not taken into account:
Line break;
Unicode characters inserted directly into the text (eg \u000a).
The code is below.
Repository
suspend fun getData(): Response<List<Model>> =
suspendCoroutine { cont ->
val collection =
firestore
.collection(COLLECTION_NAME)
.whereEqualTo(DEFAULT_CONDITION_FIELD, DEFAULT_CONDITION_VALUE)
.orderBy(SORT_FIELD, SORT_DIRECTION)
.get()
collection
.addOnSuccessListener { query ->
val data = arrayListOf<Model>()
query.toObjects(ModelDomain::class.java).forEach { data.add(it.toModel()) }
cont.resume(Response.Success(data))
}
.addOnFailureListener { cont.resume(Response.Error(it)) }
}
ViewModel
private val _data: LiveData<Response<List<Model>>> = loadData()
val data get() = _data
private fun loadData(): LiveData<Response<List<Model>>> =
liveData(Dispatchers.IO) {
emit(Response.Loading)
try {
emit(repository.getData())
} catch (e: Exception) {
emit(Response.Error(e))
}
}
Model
data class ModelDomain(
var description: String = ""
) : KoinComponent {
fun toModel() =
Model(
description = description
)
}
data class Model(
val description: String
)
Part of the code has been omitted.
UPDATE
Just wrote in Notepad ++:
Copied this to Firestore:
Result:
Firestore does not, in any way, modify data that you write to it. If you write something to a document, then read the document, you will get exactly the same data that you put into it.
If you're looking at the document in the Firebase console, you will not see all carriage returns and whitespace. Those are collapsed to save space on screen when rendering large amounts of data. But if you read the data programmatically, it will definitely be exactly as you wrote it.

How to combine two kotlin data objects with shared variable into one object?

I'm making a little application which tracks cryptocurrency values at the Bittrex exchange.
For this I'm using Bittrex' public api (https://bittrex.github.io/api/v3)
Unfortunately the api doesn't provide the data I want with just one call, therefore I need to do two api calls.
What I want to achieve is to have one object containing all of the following values:
symbol (This is a shared value between both api calls, so this needs
to match)
quoteVolume
percentChange
lastTradeRate
The bold variable is part of one api call, the other values are part of the other. 'Symbol' is part of both.
I'm using kotlin coroutines and I was hoping that I don't have to use something like RxJava to get this to work.
CoroutineScope(IO).launch {
val tickers = async {
api.getTickers()
}.await()
val markets = async {
api.getMarkets()
}.await()
val result = mutableListOf<Market>()
for (ticker in tickers.data) {
for (market in markets.data) {
if (ticker.symbol == market.symbol) {
result.add(
Market(
ticker.symbol,
ticker.lastTradeRate,
market.quoteVolume,
market.percentChange
)
)
}
}
}
}
You can make the 2 calls in parallel using coroutines.
Assuming firstApi and secondApi are suspend functions that return the data for each of the 2 blobs of info you need,
val data1Deferred = async { firstApi() }
val data2Deferred = async { secondApi() }
val data1 = data1Deferred.await()
val data2 = data2Deferred.await()
val result = Result(
// build result from data1 and data2
)
You would also need to add error handling.
Edit:
you can group your list by the symbol and generate a map:
val marketMap = markets.associateBy { it.symbol }
Then for each ticker you can get the corresponding market
for (ticker in tickers) {
val market = marketMap[ticker.symbol]
if (market != null) {
// join ticker and market
}
}

Android - Populate values into already created POJO model object using retrofit

I have a POJO model object "apiResponse" that has values from a previous API call.
{
"status":200,
"userModel":{
...SOME VARIABLES...
},
"otherContentRelatedToUserModel":{
..SOME RELATED CONTENT..
}
}
This apiResponse has an "UserModel" as an inner object.
What i want to do is pass this "apiResponse" object to another api call whose response is "UserModel", and have it update only the UserModel object in the APIResponse POJO object.
The objective is to keep a single source of related content, which could change based on the interaction in the application, but might not update the rest of the related content.
Or is it possible to atleast update an already created pojo model as a whole, updating the variable values in the model.?
Reason for this ::
The API's content does not change for a set amount of time in the server, mainly to avoid over traffic to the server. So some amount of logic has to be implemented on the application side. Currently using a DB is not really a viable option.
Basically update only a portion of the already created POJO class object with another api call.
Is this possible in android(kotlin) using retrofit? Or is there any other way this could be achievable?
I think it is not possible to populate the additional fields in the existing UserModel object by using Retrofit, but you can do some magic with GSON:
data class UserModel(
val userId: Int? = null,
val userName: String? = null)
class OtherContentRelatedToUserModel
data class ApiResponsePojo(
val status: Int? = null,
val userModel: UserModel? = null,
val otherContentRelatedToUserModel: OtherContentRelatedToUserModel? = null)
class UserModelInstanceCreator(var userModelToUpdate: UserModel? = null)
: InstanceCreator<UserModel> {
override fun createInstance(type: Type?): UserModel {
return userModelToUpdate ?: UserModel()
}
}
val apiResponseJson =
"""
{
"status":200,
"userModel":{
"userId": 1
},
"otherContentRelatedToUserModel":{
}
}
"""
val userModelResponseJson =
"""
{
"userName": "john wick"
}
"""
val userModelInstanceCreator = UserModelInstanceCreator()
val gson = GsonBuilder()
.registerTypeAdapter(UserModel::class.java, userModelInstanceCreator)
.create()
val apiResponse: ApiResponsePojo = gson.fromJson(apiResponseJson, ApiResponsePojo::class.java)
userModelInstanceCreator.userModelToUpdate = apiResponse.userModel
gson.fromJson(userModelResponseJson, UserModel::class.java)
...
// apiResponse.toString() result
// ApiResponsePojo(status=200, userModel=UserModel(userId=1, userName=john wick)...

Exactly ONE TO ONE Relation in Room

I believe it have been asked several times but no working solution.
Room has #Relation annotation which is used for one to many relationships. That's basically if you have User and Pet models, as User can have several Pets, #Relation annotation works perfectly as return type of Pets are list (or set).
class UserAndAllPets : User() {
#Relation(parentColumn = "id", entityColumn = "userId")
var pets: List<Pet> = arrayListOf()
}
The problem is what if in my case User and Pet is one to one related. As in every user can have one pet. That means there is no point of using #Relation as it only supports list or set return types. And it's totally inefficient to use as a list, even if I use it. So I am looking for a exact one to one relation where I can get a result of
class UserAndPet {
var user: User? = null
var pet: Pet? = null
}
I have tried tried several was as well as this method (which has lots of upvotes, but it doesn't work).
Supposedly,
class UserAndPet {
#Embedded
var user: User? = null
#Embedded
var pet: Pet? = null
}
Should work, but I am getting
Not sure how to convert a Cursor to this method's return type (UserAndPet)
There is no conflict as I already use prefix for #Embedded fields.
And please, can you not redirect my to any other post on stack overflow, as I tried all but no luck.
Thanks
This feature has been added as of 2.2.0-alpha01 of room.
Ref - Room release 2.2.0-alpha01
When we considering 1 to 1 relationship below approach is also possible and for more please follow the link.
#Entity(tableName = "user")
class User(
val id: Int
// ...
) {
#Ignore /* Ignores the marked element from Room's processing logic. */
var pet: Pet? = null
}
#Entity(tableName = "pet")
class Pet(
val id: Int,
val userId: Int
)
/* Single task used for 1 to 1 read */
class UserWithPetReadTask : RxTask.CallableWithArgs() {
private var result = UserWithPetTaskResult()
override fun call(params: Array<out Any>?): Any {
App.mApp?.getDBLocked { appDb ->
/* Read user details from repo */
val user: User? = appDb?.getUserDao()?.getUserById("[userId]")
user.let {
/* Read pet associated and assign it to user*/
it?.pet = appDb?.getPetDao().getPetAssociated("[userId] or [user?.id]")
result.user = user
result.isSuccess = true
}
}
return result
}
}
class UserWithPetTaskResult {
var isSuccess = false
var user: User? = null
}

Categories

Resources