Not able to return a desired value inside a overidden method using coroutines in kotlin - kotlin

I am new to kotlin and coroutines.I have been working on a client-server part of an android app.I am using mediasoup-client-android library for this.
I am trying to intialize the sendtransports using createSendTransport() method.This method does have a sendTransportListener which has abstract methods.One of them being is onProduce() which returns a producerId which is a String. awaitEmit() is an asynchronous action and I need this in onProduce().To use awaitEmit() I used coroutines.But I need String as return type instead of Deferred.Is there any other way to implement the mentioned logic? Below is my code
class RoomClient {
suspend fun initTransports(device: Device) {
coroutineScope {
val id: String?
val iceParameters: String?
val iceCandidates: String?
val dtlsParameters: String?
val sctpParameters: String?
try {
val params = JSONObject()
params.put("forceTcp",false)
params.put("rtpCapabilities", this#RoomClient.device?.rtpCapabilities)
val res = socket?.awaitEmit("createWebRtcTransport",params)
val data = res?.get(0) as JSONObject
if (data.has("error")) {
Log.d(TAG, data.getString("error"))
return#coroutineScope
}
id = data.optString("id")
iceParameters = data.optString("iceParameters")
iceCandidates = data.optString("iceCandidates")
dtlsParameters = data.optString("dtlsParameters")
sctpParameters = data.optString("sctpParameters")
} catch (e: Throwable) {
Log.e(TAG, "${e.message}")
return#coroutineScope
}
val sendTransportListener: SendTransport.Listener = object : SendTransport.Listener {
private val listenerTAG = TAG.toString() + "_ProducerTrans"
override fun onProduce(
transport: Transport,
kind: String?,
rtpParameters: String?,
appData: String?
): String? {
this#coroutineScope.async{
var producerId: String? = null
Log.d(listenerTAG, "onProduce() ")
val producerDeferred = launch {
val params = JSONObject("""{"producerTransportId": transport.id, "kind": kind, "rtpParameters": rtpParameters,"appData": appData}""")
val res = socket?.awaitEmit("produce",params)
val data = res?.get(0) as JSONObject
producerId = data.getString("producer_Id")
}
producerDeferred.join()
Log.d(listenerTAG, "producerId inside the coroutine: $producerId"
}
return#async producerId
}
}
this#RoomClient.producerTransport = device.createSendTransport(
sendTransportListener, id,
iceParameters,
iceCandidates,
dtlsParameters
)
}
}
}
And also I am not sure about the way coroutines are used here.Please correct me If I have missed something central.

Related

How to use the information stored in mutableStateOf in Jetpack Compose

I have information in json and I retrieve it using retrofit2, everything works fine, I get the data in a List.
I need this information to fill elements in Jetpack Compose for which I use mutableStateOf to save the states.
My function that I use is the following:
fun jsonParsing(
dataRecox: MutableState<List<Event>>
) {
val TAG_LOGS = "Mariox"
val retrofit = Retrofit.Builder()
.baseUrl("http://myserversample.com/pGet/track/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofitAPI = retrofit.create(APIService1::class.java)
retrofitAPI.getRecolector().enqueue(object : Callback<List<Event>> {
override fun onResponse(
call: Call<List<Event>>,
response: Response<List<Event>>
) {
val data = response.body()
val mydata = data!!
dataRecox.value = mydata
Log.i(TAG_LOGS, Gson().toJson(data))
}
override fun onFailure(call: Call<List<Event>>, t: Throwable) {
t.printStackTrace()
}
})
}
Mymodel:
data class Event (
val deviceID : Int,
val statusCode : Int,
val accountID : String,
val speedKPH : Int,
.
.
.
}
My composable:
#Composable
fun Greeting(name: String) {
val dataRecox = remember {
mutableStateOf(emptyList<Event>())
}
jsonParsing(dataRecox)
println("======")
println(dataRecox) // ok data
println(dataRecox.value). // ok data
//Uncommenting println(dataRecox.value[0]) I get empty.
//println(dataRecox.value[0])
//Text(text = dataRecox.value[0].uniqueID)
}
When I do not use the information in the console, by calling Greeting("Android") all the data is printed correctly:
The problem comes when I want to use that information:
For example, if I want to print in console println(dataRecox.value[0]) here it returns empty. If I want to use it with a composable Text: Text(text = dataRecox.value[0].uniqueID) it also gives me empty.
Can someone explain to me why this happens, because when I start using the information the data becomes empty.
The way you're doing is totally different of the recommended way... here's my suggestion.
Define a class to represent the screen's state.
data class ScreenState(
val events: List<Event> = emptyList(),
val error: Throwable? = null
)
Use a ViewModel to perform the API request and keep the screen state.
class EventsViewModel : ViewModel()
private val _screenState = MutableStateFlow<ScreenState>(ScreenState())
val screenState = _screenState.asStateFlow()
init {
jsonParsing()
}
fun jsonParsing() {
val TAG_LOGS = "Mariox"
val retrofit = Retrofit.Builder()
.baseUrl("http://myserversample.com/pGet/track/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofitAPI = retrofit.create(APIService1::class.java)
retrofitAPI.getRecolector().enqueue(object : Callback<List<Event>> {
override fun onResponse(
call: Call<List<Event>>,
response: Response<List<Event>>
) {
val data = response.body()
Log.i(TAG_LOGS, Gson().toJson(data))
_screenState.update {
ScreenState(it.events)
}
}
override fun onFailure(call: Call<List<Event>>, t: Throwable) {
t.printStackTrace()
_screenState.update {
ScreenState(error = t)
}
}
})
}
}
Instantiate the ViewModel and use it in your screen...
#Composable
fun Greeting(name: String) {
val vm = viewModel<EventsViewModel>()
val screenState by vm.screenState.observeAsState()
LazyColumn(Modifier.fillMaxSize()) {
items(screenState.items) {
Text(it. accountID)
}
}
}

How to create an HttpResponse object with dummy values in ktor Kotlin?

I am using ktor for developing a microservice in Kotlin. For testing a method, I need to create a dummy HttpResponse (io.ktor.client.statement.HttpResponse to be specific) object with status = 200 and body = some json data.
Any idea how I can create it?
You can use mockk or a similar kind of library to mock an HttpResponse. Unfortunately, this is complicated because HttpRequest, HttpResponse, and HttpClient objects are tightly coupled with the HttpClientCall. Here is an example of how you can do that:
val call = mockk<HttpClientCall> {
every { client } returns mockk {}
coEvery { receive(io.ktor.util.reflect.typeInfo<String>()) } returns "body"
every { coroutineContext } returns EmptyCoroutineContext
every { attributes } returns Attributes()
every { request } returns object : HttpRequest {
override val call: HttpClientCall = this#mockk
override val attributes: Attributes = Attributes()
override val content: OutgoingContent = object : OutgoingContent.NoContent() {}
override val headers: Headers = Headers.Empty
override val method: HttpMethod = HttpMethod.Get
override val url: Url = Url("/")
}
every { response } returns object : HttpResponse() {
override val call: HttpClientCall = this#mockk
override val content: ByteReadChannel = ByteReadChannel("body")
override val coroutineContext: CoroutineContext = EmptyCoroutineContext
override val headers: Headers = Headers.Empty
override val requestTime: GMTDate = GMTDate.START
override val responseTime: GMTDate = GMTDate.START
override val status: HttpStatusCode = HttpStatusCode.OK
override val version: HttpProtocolVersion = HttpProtocolVersion.HTTP_1_1
}
}
val response = call.response
I did this with following. I only needed to pass a status code and description, so I didn't bother about other fields.
class CustomHttpResponse(
private val statusCode: Int,
private val description: String
) :
HttpResponse() {
#InternalAPI
override val content: ByteReadChannel
get() = ByteReadChannel("")
override val call: HttpClientCall
get() = HttpClientCall(HttpClient())
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
override val headers: Headers
get() = Headers.Empty
override val requestTime: GMTDate
get() = GMTDate()
override val responseTime: GMTDate
get() = GMTDate()
override val status: HttpStatusCode
get() = HttpStatusCode(statusCode, description)
override val version: HttpProtocolVersion
get() = HttpProtocolVersion(name = "HTTP", major = 1, minor = 1)}
With Ktor 2, it's best to use externalServices block instead of attempting to mock HttpResponse. That way you don't need to attempt and mock the internals of Ktor, and it's not complicated at all.
externalServices {
hosts("https://your-fake-host") {
routing {
get("/api/v1/something/{id}/") {
call.respondText(
"{}",
contentType = ContentType.Application.Json,
status = HttpStatusCode.OK
)
}
}
}
}
This need to be wrapped with testApplication

How to emit from a LiveData builder from a non-suspending callback function

I'm new to LiveData and Kotlin Coroutines. I'm trying to use the Chromium Cronet library to make a request from my repository class to return a LiveData object. To return the liveData, I'm using the new LiveData builder (coroutines with LiveData). How would I emit the result from a successful Cronet request?
class CustomRepository #Inject constructor(private val context: Context, private val gson: Gson) : Repository {
private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
override suspend fun getLiveData(): LiveData<List<MyItem>> = liveData(coroutineDispatcher) {
val executor = Executors.newSingleThreadExecutor()
val cronetEngineBuilder = CronetEngine.Builder(context)
val cronetEngine = cronetEngineBuilder.build()
val requestBuilder = cronetEngine.newUrlRequestBuilder(
"http://www.exampleApi.com/example",
CustomRequestCallback(gson),
executor
)
val request: UrlRequest = requestBuilder.build()
request.start()
}
class CustomRequestCallback(private val gson: Gson) : UrlRequest.Callback() {
override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
byteBuffer?.flip()
byteBuffer?.let {
val byteArray = ByteArray(it.remaining())
it.get(byteArray)
String(byteArray, Charset.forName("UTF-8"))
}.apply {
val myItems = gson.fromJson(this, MyItem::class.java)
// THIS IS WHAT I WANT TO EMIT
// emit(myItems) doesn't work since I'm not in a suspending function
}
byteBuffer?.clear()
request?.read(byteBuffer)
}
// other callbacks not shown
}
}
The solution involves wrapping the UrlRequest.Callback traditional callback structure in a suspendCoroutine builder.
I also captured my learning in a Medium article which discusses Cronet integration with LiveData and Kotlin Coroutines.
override suspend fun getLiveData(): LiveData<List<MyItem>> = liveData(coroutineDispatcher) {
lateinit var result: List<MyItem>
suspendCoroutine<List<MyItem>> { continuation ->
val requestBuilder = cronetEngine.newUrlRequestBuilder(
"http://www.exampleApi.com/example",
object : UrlRequest.Callback() {
// other callbacks not shown
override fun onReadCompleted(request: UrlRequest?, info: UrlResponseInfo?, byteBuffer: ByteBuffer?) {
byteBuffer?.flip()
byteBuffer?.let {
val byteArray = ByteArray(it.remaining())
it.get(byteArray)
String(byteArray, Charset.forName("UTF-8"))
}.apply {
val myItems = gson.fromJson(this, MyItem::class.java)
result = myItems
continuation.resume(result)
}
byteBuffer?.clear()
request?.read(byteBuffer)
},
executor
)
val request: UrlRequest = requestBuilder.build()
request.start()
}
emit(result)
}

Kotlin vert.x parsing a JSON String to a Data class using gson always returns null

I am just playing around with vert.x 3.5.3 Kotlin and I am unable to parse a JSON string into a Data class using gson.
Here is the code
class DataVerticle : AbstractVerticle() {
override fun start(startFuture: Future<Void>) {
data class Product(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("productCode") val productCode: String
)
val products: MutableList<Product> = mutableListOf()
val gson = Gson()
val eventBus = vertx.eventBus()
eventBus.consumer<String>("data.verticle") {
when (it.headers().get("ACTION")) {
"ADD_PRODUCT" -> {
val prodJson = it.body()
if (prodJson != null) {
println(prodJson)
val product = gson.fromJson(prodJson, Product::class.java)
println(product)
it.reply("SUCCESS")
}
}
else -> {
print("ERROR")
}
}
}
startFuture.complete()
}
}
The Problem is the parsed value is always null
Here is my sample json ->
{"id":1,"name":"SOAP","productCode":"P101"}
The json string sent over the eventBus is not null.
I am using this package for gson
com.google.code.gson', version: '2.8.5'
Thanks
You declare your class inside the method body, which Gson doesn't like much.
Extracting it to be nested class will work just fine:
class DataVerticle : AbstractVerticle() {
override fun start(startFuture: Future) {
val gson = Gson()
val eventBus = vertx.eventBus()
eventBus.consumer<String>("data.verticle") {
when (it.headers().get("ACTION")) {
"ADD_PRODUCT" -> {
val prodJson = it.body()
if (prodJson != null) {
println(prodJson)
val product = gson.fromJson(prodJson, Product::class.java)
println(product)
it.reply("SUCCESS")
}
}
else -> {
print("ERROR")
}
}
}
startFuture.complete()
}
data class Product(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("productCode") val productCode: String
)
}
Tested with:
val vertx = Vertx.vertx()
vertx.deployVerticle(DataVerticle()) {
val options = DeliveryOptions()
options.addHeader("ACTION", "ADD_PRODUCT")
vertx.eventBus().send("data.verticle", """{"id":1,"name":"SOAP","productCode":"P101"}""", options)
}

Not able to call Multiple API Parallely using Retrofit kotlin rxjava and how to call flatMap and zip together with Common Place error handling

What i want to achive is
Call IndexList API and display the responded list in a spinner -> Click on Spinner item i want to call 3 api in parallel using .zip operator and display in DetailViewModel
I am using flatmap for calling api in sequence
In first flatmap i am calling IndexList api and i am passing IndexList response to call 3 multiple api in parallel using Observable.zip operator but i am not able
to achive the second part of calling 3 API in parallel and display in view model
Code Snippet:
Spinner Click :
spinner_index_list?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
//Performing action onItemSelected and onNothing selected
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
mRecyclerView?.visibility = View.GONE
//Select item from spinner calling API
val symbolDefaultGroup = SymbolForDefaultGroup(indexExchangeSegmentList[pos], parent.getItemAtPosition(pos).toString())
grpIndexViewModel.getSearchSymbol(symbolDefaultGroup)?.observe(this#FragmentWatchlist, Observer { instrumentByIdResponse ->
mDataProvider = ExpandableDataProvider(instrumentByIdResponse)
mRecyclerView?.visibility = View.VISIBLE
if (instrumentByIdResponse != null)
initRecyclerView(savedInstanceState)
})
}
ViewModel :
fun getSearchSymbol(symbolForDefaultGroup: SymbolForDefaultGroup): LiveData<List<InstrumentByIdResponse>>? {
instrumentByIdResponse = null
instrumentByIdResponse = MutableLiveData<List<InstrumentByIdResponse>>()
instrumentByIdResponse = groupRepository.getSearchSymbol(symbolForDefaultGroup)
L.d("get symbol for default group method")
return instrumentByIdResponse
}
Repository:
override fun getSearchSymbol(symbolForDefaultGroup: SymbolForDefaultGroup): LiveData<List<InstrumentByIdResponse>> {
val mutableLiveData = MutableLiveData<List<InstrumentByIdResponse>>()
val instrumentsIdList = ArrayList<Instrument>()
val marketDataQuotesList = ArrayList<QuotesList>()
val subscriptionList = ArrayList<SubscriptionList>()
val symbolFroDefaultGroup: Observable<BaseResponse<SymbolForDefaultGroupResponse>> = remoteServices.requestSymbolForDefaultGroup(symbolForDefaultGroup)
symbolFroDefaultGroup
.flatMap { response ->
for (i in 0 until response.result!!.instruments.size) {
instrumentsIdList.add(Instrument(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
marketDataQuotesList.add(QuotesList(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
subscriptionList.add(SubscriptionList(EnumConfig.getExchangeSegment(response.result.exchangeSegment)!!.toInt(), response.result.instruments[i].toInt()))
}
val instrumentByIdResult = InstrumentById(instrumentsIdList)
val marketDataQuotes = MarketDataQuotes("ABC", "ABC", marketDataQuotesList, 1111)
val subscriptionList = Subscribe("ABC", "ABC", "Mobile", subscriptionList, 1111)
return#flatMap getDetails(instrumentByIdResult, marketDataQuotes, subscriptionList)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : ErrorCallBack<BaseResponse<List<InstrumentByIdResponse>>>() {
override fun onSuccess(t: BaseResponse<List<InstrumentByIdResponse>>) {
L.d("Success of Search Instrument")
mutableLiveData.value = transform1(t)
}
})
return mutableLiveData
}
private fun transform1(response: BaseResponse<List<InstrumentByIdResponse>>?): List<InstrumentByIdResponse>? {
return response!!.result!!.toList()
}
fun getDetails(instrumentById: InstrumentById, marketDataQuotes: MarketDataQuotes, subscription: Subscribe): Observable<DetailsModel> {
return Observable.zip(
remoteServices.requestInstrumentById(instrumentById),
remoteServices.requestMarketDataQuotes(marketDataQuotes),
remoteServices.requestSubscribe(subscription),
/*Observable.fromArray(remoteServices.requestInstrumentById(instrumentById)),
Observable.fromArray(remoteServices.requestMarketDataQuotes(marketDataQuotes)),
Observable.fromArray(remoteServices.requestSubscribe(subscription)),*/
Function3<List<InstrumentByIdResponse>, MarketDataQuotesResponse, SubscribeResult, DetailsModel>
{ instrumentByIdResponse, marketDataQuotesResponse, subscribeResponse ->
createDetailsModel(instrumentByIdResponse, marketDataQuotesResponse, subscribeResponse)
})
}
private fun createDetailsModel(instrumentByIdResponse: List<InstrumentByIdResponse>, marketDataQuotesResponse: MarketDataQuotesResponse, subscribeResult: SubscribeResult): DetailsModel {
return DetailsModel(instrumentByIdResponse, marketDataQuotesResponse, subscribeResult)
}
data class DetailsModel(
val instrument: List<InstrumentByIdResponse>,
val marketDataQuotes: MarketDataQuotesResponse,
val subscribe: SubscribeResult)
RemoteService :
fun requestInstrumentById(instrumentById: InstrumentById) = ApiService.requestInstrumentById(instrumentById)
APIService :
#Headers("Content-Type: application/json")
#POST("search/instrumentsbyid")
fun requestInstrumentById(#Body instrumentById: InstrumentById): Observable<BaseResponse<List<InstrumentByIdResponse>>>
API Structure
Error that i get on Zip Operator