Completion block with multiple params in Kotlin - kotlin

In my application I use Retrofit to make a network call and I pass a completion block to the wrapping function:
fun issueToken(phoneNumber: String, otp: String,completion:(token: Token?, errorString: String) -> Unit) { //TODO: return something??
retrofitInstance.issueToken(LOGIN_GRANT_TYPE, LOGIN_CLIENT_ID,LOGIN_CLIENT_SECRET,phoneNumber, otp).enqueue(object : retrofit2.Callback<IssueTokenResponse> {
override fun onFailure(call: retrofit2.Call<IssueTokenResponse>, t: Throwable) {
completion(null, "NetworkManger: issueToken failed")
}
override fun onResponse(call: retrofit2.Call<IssueTokenResponse>, response: retrofit2.Response<IssueTokenResponse>) {
if (response.code() == 200) {
AccountManager.token.mAccessToken = response.body()?.access_token
AccountManager.token.mRefreshToken = response.body()?.refresh_token
AccountManager.token.mExpirationDate =
response.body()?.expires_in?.plus((System.currentTimeMillis() / 1000L));
completion(AccountManager.token, "")
} else {
//TODO
completion(null, "NetworkManager: issueToken response code = " + response.code() + " message = " + response.message() )
}
}
})
}
Now, I'm not sure how to call the issueToken method with the completion block, as I am new to Kotlin. I tried something like that:
NetworkManager.issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString()) token, errorString -> { ... }
But I get errors that says:
No value passed for parameter 'completion'
I also have errors on this part:
token, errorString ->
What is the correct way to call the function with the completion block?

I suggest you to read kotlin lambda conventions first.
You should call like that:
issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString()) { token: Token?, errorString: String ->
}
You may also use like that:
val completion = { token: Token?, errorString: String ->
// define whatever you need
}
issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString(), completion)

Related

Getting data from Datastore for injection

I am trying to retrieve the base url from my proto datastore to be used to initialize my ktor client instance I know how to get the data from the datastore but I don't know how to block execution until that value is received so the client can be initialized with the base url
So my ktor client service asks for a NetworkURLS class which has a method to return the base url
Here is my property to retrieve terminalDetails from my proto datastore
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
Normally when I want to get the values I would do something like this
private fun getTerminalDetailsFromStore() {
try {
viewModelScope.launch(Dispatchers.IO) {
localRepository.getTerminalDetails.collect {
_terminalDetails.value = it
}
}
} catch(e: Exception) {
Log.d("AdminSettingsViewModel Error", e.message.toString()) // TODO: Handle Error Properly
}
}
but in my current case what I am looking to do is return terminalDetails.backendHost from a function and that where the issue comes in I know I need to use a coroutine scope to retrieve the value so I don't need to suspend the function but how to a prevent the function returning until the coroutine scope has finished?
I have tried using async and runBlocking but async doesn't work the way I would think it would and runBlocking hangs the entire app
fun backendURL(): String = runBlocking {
var url: String = "localhost"
val job = CoroutineScope(Dispatchers.IO).async {
repo.getTerminalDetails.collect {
it.backendHost
}
}
url
}
Can anyone give me some assistance on getting this to work?
EDIT: Here is my temporary solution, I do not intend on keeping it this way, The issue with runBlocking{} turned out to be the Flow<T> does not finish so runBlocking{} continues to block the app.
fun backendURL(): String {
val details = MutableStateFlow<TerminalDetails>(TerminalDetails.getDefaultInstance())
val job = CoroutineScope(Dispatchers.IO).launch {
repo.getTerminalDetails.collect {
details.value = it
}
}
runBlocking {
delay(250L)
}
return details.value.backendHost
}
EDIT 2: I fully fixed my issue. I created a method with the same name as my val (personal decision) which utilizes runBlocking{} and Flow<T>.first() to block while the value is retrieve. The reason I did not replace my val with the function is there are places where I need the information as well where I can utilize coroutines properly where I am not initializing components on my app
val getTerminalDetails: Flow<TerminalDetails> = cxt.terminalDetails.data
.catch { e ->
if (e is IOException) {
Log.d("Error", e.message.toString())
emit(TerminalDetails.getDefaultInstance())
} else {
throw e
}
}
fun getTerminalDetails(): TerminalDetails = runBlocking {
cxt.terminalDetails.data.first()
}

rxjava, how to inspect the result of a Single

using kotlin, having code
fun fetchRemoteDataApi(): Single<RemoteDataResponse> = networkApi.getData()
// it is just a retrofit
#GET(".../api/getData")
fun getData() : Single<RemoteDataResponse>
fun mergeApiWithDb(): Completable = fetchRemoteDataApi()
.zipWith(localDao.getAll())
.flatMapCompletable { (remoteData, localData) ->
doMerge(remoteData, localData) //<== return a Completable
}
the code flow:
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache() //<=== would like do some inspection at this level
PublishSubject.create<Unit>().toFlowable(BackpressureStrategy.LATEST)
.compose(Transformers.flowableIO())
.switchMap {
//merge DB with api, or local default value first then listen to DB change
mergeApiDbCall.andThen(listAllTopics())
.concatMapSingle { topics -> remoteTopicUsers.map { topics to it } }
}
.flatMapCompletable { (topics, user) ->
// do something return Completable
}
.subscribe({
...
}, { throwable ->
...
})
and when making the call
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache()
the question is if would like to inspect on the Singles<RemoteDataResponse> returned from fetchRemoteDataApi() (i.e. using Log.i(...) to printout the content of RemoteDataResponse, etc.), either in got error or success case, how to do it?
/// the functions
fun listAllTopics(): Flowable<List<String>> = localRepoDao.getAllTopics()
// which a DAO:
#Query("SELECT topic FROM RemoteDataTable WHERE read = 1")
fun getAllTopics(): Flowable<List<String>>
///
private val remoteTopicUsers: Single<List<User>>
get() {
return Single.create {
networkApi.getTopicUsers(object : ICallback.IGetTopicUsersCallback {
override fun onSuccess(result: List<User>) = it.onSuccess(result)
override fun onError(errorCode: Int, errorMsg: String?) = it.onError(Exception(errorCode, errorMsg))
})
}
}
You cannot extract information about elements from the Completable. Though you can use doOnComplete() on Completable, it will not provide you any information about the element.
You can inspect elements if you call doOnSuccess() on your Single, so you need to incorporate this call earlier in your code. To inspect errors you can use doOnError() on both Completable or Single.

Retrofit's `enqueue()` doesn't re-assign value of object in time for the return statement?

I'm making a call to the API and the response body is assigned to an object inside Retrofit's enqueue(), the problem is that enqueue finishes too quickly for the value to be assigned before the return statement of the function body is called.
Previously, I was using MutableLiveData before and it took care of that because it's always observing the data and when it changes it assigns it with no problem but now I don't want to use any MutableLiveData or Observables because I'm trying to prepare the data before any UI is actually drawn on the screen.
fun getResponse(
weatherLocationCoordinates: WeatherLocation
): RequestResponse {
weatherApiService.getCurrentWeather(
weatherLocationCoordinates.latitude,
weatherLocationCoordinates.longitude
).enqueue(object : Callback<WeatherResponse> {
override fun onResponse(
call: Call<WeatherResponse>,
response: Response<WeatherResponse>
) {
if (response.isSuccessful) {
// This where I do the assigning
requestResponse = RequestResponse(response.body(), true)
}
}
override fun onFailure(call: Call<WeatherResponse>, t: Throwable) {
requestResponse = RequestResponse(null, false)
}
})
// When this is called, enqueue is still not finished
// therefore I get the wrong value, I get the previously set initialization value of the obj.
return requestResponse
}
Should I be using Callbacks or something else? I'm not sure on how to implement the callback.
Following up on the comments here's an approach with callbacks:
Let's suppose we change the method signature to:
fun getResponse(
weatherLocationCoordinates: WeatherLocation,
onSuccess: (WeatherResponse) -> Unit = {},
onError: (Throwable) -> Unit = {}
) {
weatherApiService.getCurrentWeather(
weatherLocationCoordinates.latitude,
weatherLocationCoordinates.longitude
).enqueue(object : Callback<WeatherResponse> {
override fun onResponse(
call: Call<WeatherResponse>,
response: Response<WeatherResponse>
) {
if (response.isSuccessful) {
onSuccess(response.body())
} else {
onError(CustomHttpExceptionWithErrorDescription(response))
}
}
override fun onFailure(call: Call<WeatherResponse>, t: Throwable) {
onError(t)
}
})
}
CustomHttpExceptionWithErrorDescription will have to be something you code that can simply parse the error gotten from the server. Anything that is not 2XX status code
This method accepts 2 extra parameters - one gets called upon success the other on error. The idea is to call it like:
getResponse(
weatherLocationCoordinates,
onSuccess = {
// do something with response
},
onError = {
// do something with the error
}
)
Because they have default parameters you actually don't need to specify both callbacks. Just the one you are interested in. Examples:
// react only to successes
getResponse(
weatherLocationCoordinates,
onSuccess = {
// do something with response
}
)
// react only to errors
getResponse(weatherLocationCoordinates) {
// do something with the error
}
// just call the network calls and don't care about success or error
getResponse(weatherLocationCoordinates)

Kotlin KCallable illegalArgumentException

I have the following Kotlin function:
fun invokeSync(typedArguments : List<Any?>): Any?{
var returnedValue : Any?
try {
returnedValue = callable.call(this, typedArguments);
} catch (e:Exception) {
logInvocationError(e, typedArguments);
throw IllegalArgumentException(e);
}
}
It doesn't matter how much arguments are in this list, I will always get an IllegalArgumentException saying "Callable expects 3 arguments, but 1 were provided".
The function is a simple isGreater-function with 2 arguments of type Int.
I have checked the list of arguments and there are 2 arguments of type Int in there.
Here the function in context:
open class TypedJavaScriptFunction(name: String) : SelfRegisteringJavascriptFunction(MessageFormat.format(JS_NAME_CONVENTION, name)) {
val callable = getCallable(this::class)
override fun function(arguments: Array<Any?>): Any? {
try {
val typedArguments = getTypedArguments(arguments)
val annotations = callable.annotations
for (a in annotations) {
if (a is BrowserFunction) {
if (a.value == Policy.ASYNC) {
invokeAsync(typedArguments);
return null
} else {
return invokeSync(typedArguments)
}
}
}
} catch (e: IllegalArgumentException) {
// this Exception is only for signaling the error; it has already
// been logged before
JavaScriptAPI.showError(browser, "Internal Error (" + callable.name + ")");
}
return null
}
fun getTypedArguments(arguments: Array<Any?>): List<Any?> {
var typedArguments = mutableListOf<Any?>()
val argTypes = callable.valueParameters
if (arguments.size != argTypes.size) {
LOG.error(getName()
+ ": given arguments don't match signature. Given: "
+ arguments.size + ", expected: " + argTypes.size);
throw IllegalArgumentException()
}
for (i in 0 until arguments.size) {
typedArguments.add(TypeRefinery.refine(arguments[i], argTypes[i].type.classifier as KClass<Any>))
}
return typedArguments
}
// ...
fun invokeSync(typedArguments: List<Any?>): Any? {
var returnedValue: Any?
try {
returnedValue = callable.call(this, typedArguments);
} catch (e: Exception) {
logInvocationError(e, typedArguments);
throw IllegalArgumentException(e);
}
// ...
}
}
Did anyone can help me and tell me whats wrong or can give me a hint?
Since call takes a vararg you need to use the spread operator * and toTypedArray() to pass in the List like that:
returnedValue = callable.call(this, *typedArguments.toTypedArray());
The first argument is the instance you are calling the function on and the other two parameters come from the spreaded List, under the condition that List has exactly two elements.

RxAndroid - Handle Errors with Zip operator

I'm trying to find a way to execute requests in parallel and handle them when every observable finishes. Despite everything is working when all observables gives a response, I not seeing a way to handle each all errors when everything is finished.
This is a sample of zip operator, which basically executes 2 requests in parallel:
Observable.zip(
getObservable1()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.ONE, errorThrowable))
}.subscribeOn(Schedulers.io()),
getObservable2()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.TWO, errorThrowable))
}.subscribeOn(Schedulers.io()),
BiFunction { value1: String, value2: String ->
return#BiFunction value1 + value2
})
//execute requests should be on io() thread
.subscribeOn(Schedulers.io())
//there are other tasks inside subscriber that need io() thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
Snackbar.make(view, "Replace with your own action " + result, Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
},
{ error ->
Log.d("TAG", "Error is : " + (error as ErrorEntity).error.message)
}
)
private fun getObservable1(): Observable<String> {
return Observable.defer {
throw Throwable("Error 1")
}
}
private fun getObservable2(): Observable<String> {
return Observable.defer {
throw Throwable("Error 2")
}
}
Problem with this approach is that there is no mechanism to join each error like BiFunction do for the success case. Therefore, the zip operator will only trigger the first error and will ignore the others.
Output:
D/TAG: Error is : Error 1
Is there any way to retrieve all errors only after every observable inside zip completed or gave an error?
My main goal is to see which requests gave an error and execute only those after a dialog appears to the user asking him if he wants to retry the failed requests.
You can model your observables using data classes. E.g.
sealed class Response {
data class Success(val data: String) : Response()
data class Error(val t: Throwable) : Response()
}
then you can map your observables to Response like this:
val first: Observable<Response> = observable1
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
val second: Observable<Response> = observable2
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
and you can combine them:
Observable.zip(
first,
second,
BiFunction { t1: Response, t2: Response -> Pair(t1, t2) }
).subscribe({println(it)})
this prints:
(Error(t=java.lang.Exception: Error 1), Error(t=java.lang.Exception:
Error 2))
Also take a look at this article.