How to call enqueue callback? - kotlin

I tried to use enqueue callback but it shows that as an error like "Type mismatch.
Required: Callback<*List<-Location>>"
and this is exactly what I wrote.
fun searchLocation(searchString: String){
showProgress.value = true
val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(WeatherNetwork::class.java)
service.getLocation(searchString).enqueue(object : Callback<List<Location>>{
override fun onFailure(call: Call<List<Location>>, t: Throwable) {
}
override fun onResponse(
call: Call<List<Location>>,
response: Response<List<Location>>
) {
}
})
}
May be it'll be more clear with the pic
UPD: WeatherNetwork
const val BASE_URL = "/My API/"
interface WeatherNetwork{
#GET ("search?")
fun getLocation(SearchString : String) : List<Location>
}

Your WeatherNetwork is wrong, your api call needs to return a Call
change:
#GET ("search?")
fun getLocation(SearchString : String) : List<Location>
}
To
#GET ("search?")
fun getLocation(SearchString : String) : Call<List<Location>>
}
Side note:
I think you need to change your #GET("search?") to make it work.
For example if your REST URI is : localhost/search?st="wikipedia"
You need to update your function to:
#GET ("search")
fun getLocation(#Query("st") SearchString : String) : List<Location>
}
have a look at : Retrofit 2 - URL Query Parameter

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)
}
}
}

what is Best practices for designing asynchronous task in this case( in kotlin, coroutine or thread)

my android app need to call more than 10 APIs at the same time.
this call api's is use other library it made in other teams
and result receive by listener in JsonString format.
this multiple calling api is need to call at same time.
Because it takes a lot of time to call one API
i made it by callback structure like this.
but i hope refactor this code covert to coroutine.
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
private val listener = object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) {
when (apiName) {
"UserInfo" -> handleResultUserInfo(result)
"ProductInfo" -> handleResultProductInfo(result)
"....Info" -> handleResult___Info(result)
// ... and Others
}
}
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
fun start() {
callAPI("UserInfo")
callAPI("ProductInfo")
// ... and Others
}
fun callAPI(apiName: String, listener: ApiListener? = null) {
val listener = listener ?: this.listener
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
fun retry(apiName: String, listener: ApiListener) {
if (retryCount[apiName]!! < 3) {
retryCount[apiName]!!.plus(1)
library.callApi(apiName, listener)
}else{
throw RuntimeException("API Call Failed: $apiName")
}
}
fun handleResultUserInfo(result: String?) {
// TODO parse & do something
}
fun handleResultProductInfo(result: String?) {
// TODO parse & do something
}
fun handleResult___Info(result: String?) {
// TODO parse & do something
}
// ... and Others
i want use coroutine for readability not callback structure.
callback structure is not good method for readability i think.
so, i applied suspendCoroutine to library's listener for look like synchronous readability.
but, suspendCoroutine is suspend it functions when to until call it.resume
what is best practice in this case?
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
fun start(){
CoroutineScope(Dispatchers.IO).launch{
handleResultUserInfo(callAPI("UserInfo"))
handleResultProductInfo(callAPI("ProductInfo"))
handleResult___Info(callAPI("___Info"))
}
}
suspend fun callAPI(apiName: String, listener:ApiListener? = null) : String? = suspendCoroutine{
val listener = listener ?: object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) = it.resume(result)
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
↑ it waiting complete of previous work. it's not call api at same time
so i try to like this.
fun start(){
val callDataArr = arrayOf(
CallData("UserInfo", ::handleResultUserInfo),
CallData("ProductInfo", ::handleResultProductInfo),
CallData("___Info", ::handleResult___Info),
// ... and others
)
callDataArr.forEach {
CoroutineScope(Dispatchers.IO).launch{
it.handler(callAPI(it.apiName))
}
}
}
but... it doesn't look good.
because, CoroutineScope(Dispatchers.IO).launch called a lot of times
is not good for performance or have other problems?

Is it possible to stop the flow 's collection from collect's code block?

I am a newbie in coroutine/flow and would like to know the appropriate way to close the flow from the collect's code block when it gets the value it wanted.
The code like this:
suspend fun findService(scope:CoroutineScope, context:Context, name:String) {
val flow = getWifiDebuggingConnectDiscoveryFlow( context )
try {
flow.collect {
if(name == it.serviceName) {
/* need to exit the collection and execute the code that follows */
}
}
println("service found!")
} catch(e: Throwable) {
println("Exception from the flow: $e")
}
/* need to do something after service found */
}
private fun getWifiDebuggingConnectDiscoveryFlow(context:Context) = callbackFlow {
val nsdManager:NsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
val listener = object : NsdManager.DiscoveryListener {
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStartDiscoveryFailed")}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStopDiscoveryFailed")}
override fun onDiscoveryStarted(serviceType: String?) {}
override fun onDiscoveryStopped(serviceType: String?) {}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
if(serviceInfo==null) return
trySend(serviceInfo)
}
}
nsdManager.discoverServices(ServiceDiscovery.ADB_CONNECT_TYPE, NsdManager.PROTOCOL_DNS_SD, listener)
awaitClose { nsdManager.stopServiceDiscovery(listener) }
}
This problem has been bothering me for a long time, and I would appreciate any help I get.
You can use the first or firstOrNull operators. It will stop collecting as soon as the first element that complies the condition is received:
val service = flow.firstOrNull { name == it.serviceName }
...
You can find first official documentation here

How to return an object from an api-call-function in Kotlin? [duplicate]

How can I return a value after a callback in kotlin, I tried using Thread.sleep but it doesn't work
fun searchColorFromAPI(): Colors {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
mColors = serviceResponse
}
else {
//buildToast(getString(R.string.null_response))
}
}
else {
//buildToast(getString(R.string.response_unsuccessful))
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
/* buildToast(getString(R.string.error_calling_service))
Log.e(TAG, t?.message)*/
}
})
return mColors
}
Always, the mColors is returned before the onFailure or onResponse because they're asynchronous. Before this code was in MainActivity but I was advised to take off, but now when I try get mColors I get the empty value before and after the onResponse is executed, please I'm still learning Kotlin and Android.
Your problem stems from the fact that Retrofit call is asynchronous, so as soon as you call searchColorFromAPI it returns you mColors but the API call may not have been made yet, so you get the mColors value before API call.
To solve this issue, you can do
Use callback, this will require little modification in your current setup, but the 2nd option is preferable over this. Using callback your function should look like this.
/* Now instead of returning a value, your function takes a function (named callback)
as parameter. when your api call finishes, you can call the callback function and
pass the api response.
*/
fun searchColorFromAPI(callback: (Colors?) -> Unit) {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
/** pass API response to callback */
callback(serviceResponse)
}
else {
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
callback(null)
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
callback(null)
}
})
}
And in your activity declare a function as follows.
// This function will be called when your api call finishes
// and it will give you the api response
fun apiCallback(colors: Colors?){
if(colors == null){
// API Call failed
}
else{
// use colors as returned by API
}
}
And now call to searchColorFromApi should look like this
searchColorFromApi(apiCallback)
Use Live Data, declare following field in your viewmodel, if you are not using viewmodel then declare it in the class which has searchColorFromApi function.
var colors: MutableLiveData<Colors> = MutableLiveData()
and modify your searchColorFromAPI function as follows
fun searchColorFromAPI() {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
colors.postValue(response.body)
}
}
else {
colors.postValue(null)
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
colors.postValue(null)
}
})
}
and in your activity do following
fun setupObservers(){
yourApiCallingClass.colors.observe(this, Observer {
// this code is called when ever value of color field changes
})
}
You can use live data ,that gets updated once the callback receives ,the same live data is observed by the caller fragment/activity
You can use coroutines to return a value from function which has asyn calls in it.
You can use interface callbacks to activity/ fragment to trigger the updates received from retrofit calls.

Square brackets after function call

In the PagingWithNetworkSample, in the RedditActivity.kt on the line 68 is a function that contains another function call followed by square brackets and the class type (line 78):
private fun getViewModel(): SubRedditViewModel {
return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val repoTypeParam = intent.getIntExtra(KEY_REPOSITORY_TYPE, 0)
val repoType = RedditPostRepository.Type.values()[repoTypeParam]
val repo = ServiceLocator.instance(this#RedditActivity)
.getRepository(repoType)
#Suppress("UNCHECKED_CAST")
return SubRedditViewModel(repo) as T
}
})[SubRedditViewModel::class.java]
}
What does this exactly do? Automatically cast to that type? (it's not an array/list to suppose it calls get)
Can you bring an example where this is useful?
That code might look strange, but it's really just a way of calling get(). This would be just as valid, but slightly more verbose:
private fun getViewModel(): SubRedditViewModel {
return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// ...
}
}).get(SubRedditViewModel::class.java)
}