Showing a progress dialog while doing multiple retrofit calls - android

I want to do a list of calls to upload a list of pictures, show a progress dialog at the beginning and dismiss it at the end. Howewer the progress dialog never show up. If I comment progresRing.dismiss() then the dialog appear but later. Is there a better way to do multiple calls than in a for loop ?
val progresRing = ProgressDialog(this#AddExtraPicturesActivity)
progresRing.isIndeterminate = true
progresRing.setTitle("Uploading pictures")
progresRing.setMessage("Please wait")
progresRing.setCancelable(false)
progresRing.show()
for (item in pictureList) {
if(item.pictureFile != null) {
val file = item.pictureFile
if(file!!.exists()) {
var fileData = Base64.encodeToString(FileUtils.readFileToString(file).toByteArray(), Base64.DEFAULT)
val transactionId = UUID.randomUUID().toString()
val tokenId = ""
val jobDocument = JobDocument("Test", "", "", "PHONE_PICTURE", "jpg", "test.jpg", "", fileData)
val requestBody = UploadDocumentRequest("Test", jobDocument)
val service = RestAPI(this#AddExtraPicturesActivity)
val request = service.uploadDocument(authorization, transactionId, tokenId, requestBody)
request.enqueue(object : Callback<UploadDocumentResponse> {
override fun onResponse(call: Call<UploadDocumentResponse>, response: Response<UploadDocumentResponse>) {
Timber.d( response.toString())
}
override fun onFailure(call: Call<UploadDocumentResponse>, t: Throwable) {
Timber.d(t.toString())
}
})
}
}
}
progresRing.dismiss()

The best way to achieve this is definitely by using Reactive Programming so you could have some sort of callback when all calls are done to perform another action.
An easier way would be to count the total number of calls you need to make and do the following:
// find here the total of calls you need to make before the loop
totalCount = ??
var = 0
// and later, as retrofit requests are asynchronous, on the last upload the condition will be valid and the progress should dismiss
request.enqueue(object : Callback<UploadDocumentResponse> {
override fun onResponse(call: Call<UploadDocumentResponse>, response: Response<UploadDocumentResponse>) {
Timber.d( response.toString())
var = var + 1
if(var == totalCount)
progresRing.dismiss()
}
override fun onFailure(call: Call<UploadDocumentResponse>, t: Throwable) {
Timber.d(t.toString())
}
})

Related

Return a value from a Listeners onSuccess() in Kotlin (Android)

I try these tutorials: https://github.com/docusign/mobile-android-sdk/blob/master/README.md, especially the function getUserSignatureInfo. In this function a REST API call (userSignaturesGetUserSignature) is made.
In my code below I try to return a value (userSignatureId) I get from REST API. I understand, it's impossible this way, because onSuccess() will be invoked later as the outer function getUserSignatureInfo() returns.
I want to call getUserSignatureInfo() from a Fragments onActivityCreated() and use this value on creating a RecyclerView.Adapter.
The question is, what is the (best practice) way to do something like this: make a REST API call, wait for response, and use the response in further code.
// my Fragment
...
...
val userSignatureId = getUserSignatureInfo()
recyclerView.adapter = createMyAdapter(userSignatureId)
...
...
// function where the REST API call is made
fun getUserSignatureInfo(context: Context) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
var userSignatureId = ""
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// My problem: this assignment is useless
// because the outer function getUserSignatureInfo()
// returns earlier as onSuccess()
userSignatureId = userSignature.signatureId
}
}
override fun onError(exception: DSRestException) {
// TODO: Handle error
}
}) {
usersApi!!.userSignaturesGetUserSignature(user.accountId, user.userId, "signature")
}
// This is my problem: userSignatureId is empty because
// onSuccess() fires later as this function returns
return userSignatureId
}
Thank you much!
You could pass a callback into getUserSignatureInfo(), for example
fun getUserSignatureInfo(context: Context, callback: (String)->Unit) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// return the value in the callback
callback(userSignature.signatureId)
}
}
When you want to use the string value from another class,
getUserSignatureInfo(context) { id ->
Log.d("test", id)
}

Add parsed data to list

So I have this function created to generate fake values for my list:
private fun generateFakeValues(): List<Torrent> {
val values = mutableListOf<Torrent>()
val torrent1 = Torrent()
torrent1.name = "Big Buck Bunny"
torrent1.downloadSpeed = 0.00
torrent1.uploadSpeed = 0.00
torrent1.downloaded = 59.23
torrent1.length = 263.64
values.add((torrent1))
return values
}
And it works just fine. Now I added a Http request and wanted to parse the data but the items are not in the list:
private fun getTorrents(): List<Torrent> {
var torrents = mutableListOf<Torrent>()
val request = Request.Builder()
.url("...")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
throw IOException("Unexpected code $response")
}
torrents = gson.fromJson(response.body!!.string(), mutableListOf<Torrent>()::class.java)
}
}
})
return torrents
}
What am I doing wrong?
Since network call happens on background thread. You have to publish data to UI thread like
activity.runOnUiThread({/*add to adapter and notify data change to adapter*/})
or
view.post({/*add to adapter and notify data change to adapter*/})

Launch instances of same coroutine one after the other

I have a scenario where I have multiple URLs to download PDFs. I need to make a network call for each url, process the pdf, send it to a printer, wait for the printer to finish and then continue with the next url. Im trying to get my head around coroutines but I cant understand how to apply it here.
Here I'm making a call for each url in an array and making a call, in the callback I call the method "printImage" where I send the pdf to a printer and I can listen for it to finish, how could I implement coroutines here? In this case the method "printImage" should be a coroutine
fun printLabels(labels: HashMap<Long, String>) {
for (item in labels.values) {
val call = labelService.getPDF(item)
call.enqueue(
object : retrofit2.Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
val outputDir = cacheDir
val outputFile = File.createTempFile("prefix", "extension", outputDir)
Files.asByteSink(outputFile).write(response.body()!!.bytes())
val bitmap = getBitmap(outputFile)!!
printImage(bitmap)
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Timber.e(t.localizedMessage)
}
}
)
}
}
fun printImage(bm: Bitmap) {
Thread(Runnable {
printerRepository.currentPrinter.value?.let { printer ->
if (printer.startCommunication()) {
val result = printer.printImage(bm)
if (result.errorCode != PrinterInfo.ErrorCode.ERROR_NONE) {
Timber.d("ERROR - " + result.errorCode)
} else {
}
printer.endCommunication()
} else {
printerRepository.printerTestPassed.postValue(false)
}
}
}).start()
}

How can I have OnCompleteListener wait in Kotlin?

I call the getFavorite method to get data from the Database Class in Activity. But It doesn't wait for onCompleteListener. So The list comes as empty. What should I do?
GetMovieActivity.java
private fun getPhotoListFromApi() {
val apiService = ApiClient.getRetrofitInstance().create(ApiService::class.java)
var list = ArrayList<MovieDetailsResponse>()
var db = Database()
db.getFavorite()
for (id in db.moviesID)
{
apiService.getMovieDetails(
id.toString().toInt(),
"922df43f1a304aca901feb9728b01943",
Locale.getDefault().language + "-" + Locale.getDefault().country)
.enqueue(object : Callback<MovieDetailsResponse> {
override fun onFailure(call: Call<MovieDetailsResponse>, t: Throwable) {
}
override fun onResponse(call: Call<MovieDetailsResponse>, response: Response<MovieDetailsResponse>) {
response.body()?.let { it ->
list.add(it)
}
}
})
}
adapter.setList((list as List<MovieListItem>?)!!)
}
Database.kt
fun getFavorite()
{
val dr = db.collection("Favorites").document(mAuth!!.uid) as DocumentReference
dr.get().addOnCompleteListener { task: Task<DocumentSnapshot> ->
if(task.isSuccessful)
{
moviesMap = task.result.data!!
moviesID = moviesMap.get("movies") as ArrayList<Any>
}
}
}
In you'r adapter you need to snd the new list always and refresh your adapter by calling notifyDataSetChange()
and in once the callback finish you can ether you use liveData to post data to it and listen to it and push the new data to the adapter and refresh it to make sure you always having the right and latest data and you can use rxJava to make sure you're working in background thread and once it finish push it to ui thread and update you'r adapter

How to call a method multiple times using RXJava - which operator do I use?

I have a function which does a network call to retrieve data, and I need to call it 5 times. My method:
#Throws(IOException::class)
private fun getWeather(future : Int): String {
var twitterURL = Constants.API_URL
if (future > 0) {
twitterURL += "future$future.json"
}
val urlBuilder = HttpUrl.parse(Constants.API_URL).newBuilder()
val url = urlBuilder.build().toString()
val request = Request.Builder()
.url(url)
.build()
val client = OkHttpClient()
val response = client.newCall(request).execute()
val body = response.body()
return if (response.code() == HttpURLConnection.HTTP_OK) {
body.string()
} else {
throw IOException("Bad Request: Server Response" + response.code().toString() + " " + response.message())
}
}
I'd like to use the observable model doing something like:
private val array = DoubleArray(5)
Observable.fromArray(listOf(1, 2, 3, 4, 5))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
.subscribe(object:Subscriber<Int>(){
override fun onCompleted() {
calculateStandardDeviation(array)
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: Int?) {
val string = getWeather(t)
val gson = Gson()
val weather = gson.fromJson(string,TwitterWeather::class.java)
array[t-1] = weather.temperature
}
})
But really, onNext() runs on the main thread. I want to run it all on the Schedulers.io() thread.
.observeOn(AndroidSchedulers.mainThread()) tells on which scheduler will the subscribe callbacks, including onNext run. So just use .observeOn(Schedulers.io())
Check this article for more details:
https://medium.com/upday-devs/rxjava-subscribeon-vs-observeon-9af518ded53a
Conceptually, you need to move your network call from the subscription logic and make it Observable, something like this:
Observable.fromIterable(listOf(1, 2, 3, 4, 5))
.flatMap { t ->
Observable.fromCallable {
val string = getWeather(t)
val gson = Gson()
val weather = gson.fromJson(string, TwitterWeather::class.java)
weather.temperature
}.subscribeOn(Schedulers.io())
}.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ temperatures ->
calculateStandardDeviation(temperatures)
}, {
//TODO : Handle error here
})

Categories

Resources