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()
}
Related
PROBLEM STATEMENT
: When i press register button for register new user it show register success response in toast from live data, but when i tried to do same button trigger it show again register success response message from API & then also show phone number exist response from API in toast. It means old response return by live data too. So how can i solve this recursive live data response return issue?
HERE is the problem video link to understand issue
Check here https://drive.google.com/file/d/1-hKGQh9k0EIYJcbInwjD5dB33LXV5GEn/view?usp=sharing
NEED ARGENT HELP
My Api Interface
interface ApiServices {
/*
* USER LOGIN (GENERAL USER)
* */
#POST("authentication.php")
suspend fun loginUser(#Body requestBody: RequestBody): Response<BaseResponse>
}
My Repository Class
class AuthenticationRepository {
var apiServices: ApiServices = ApiClient.client!!.create(ApiServices::class.java)
suspend fun UserLogin(requestBody: RequestBody) = apiServices.loginUser(requestBody)
}
My View Model Class
class RegistrationViewModel : BaseViewModel() {
val respository: AuthenticationRepository = AuthenticationRepository()
private val _registerResponse = MutableLiveData<BaseResponse>()
val registerResponse: LiveData<BaseResponse> get() = _registerResponse
/*
* USER REGISTRATION [GENERAL USER]
* */
internal fun performUserLogin(requestBody: RequestBody, onSuccess: () -> Unit) {
ioScope.launch {
isLoading.postValue(true)
tryCatch({
val response = respository.UserLogin(requestBody)
if (response.isSuccessful) {
mainScope.launch {
onSuccess.invoke()
isLoading.postValue(false)
_registerResponse.postValue(response.body())
}
} else {
isLoading.postValue(false)
}
}, {
isLoading.postValue(false)
hasError.postValue(it)
})
}
}
}
My Registration Activity
class RegistrationActivity : BaseActivity<ActivityRegistrationBinding>() {
override val layoutRes: Int
get() = R.layout.activity_registration
private val viewModel: RegistrationViewModel by viewModels()
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
//return old reponse here then also new reponse multiple time
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
override fun toolbarController() {
binding.backactiontoolbar.menutitletoolbar.text = "Registration"
binding.backactiontoolbar.menuicontoolbar.setOnClickListener { onBackPressed() }
}
override fun processIntentData(data: Uri) {}
}
your registerResponse live data observe inside button click listener, so that's why it's observing two times! your registerResponse live data should observe data out side of button Click listener -
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
}
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
LiveData is a state holder, it's not really meant to be used as an event stream. There is a number of articles however about the topic like this one which describe the possible solutions, including SingleLiveEvent implementation taken from google samples.
But as of now kotlin coroutines library provides better solutions. In particular, channels are very useful for event streams, because they implement fan-out behaviour, so you can have multiple event consumers, but each event will be handled only once. Channel.receiveAsFlow can be very convenient to expose the stream as flow. Otherwise, SharedFlow is a good candidate for event bus implementation. Just be careful with replay and extraBufferCapacity parameters.
I made app where user can add server (recycler row) to favorites. It only saves the IP and Port. Than, when user open FavoriteFragment Retrofit makes calls for each server
#GET("v0/server/{ip}/{port}")
suspend fun getServer(
#Path("ip") ip: String,
#Path("port") port: Int
): Server
So in repository I mix the sources and make multiple calls:
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
try {
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
val list: MutableList<Server> = mutableListOf()
getFavoritesServersNotLiveData.forEach { fav ->
val server = soldatApiService.getServer(fav.ip, fav.port)
list.add(server)
}
emit(DataState.Success(list))
} catch (e: Exception) {
emit(DataState.Error(e))
}
}
and then in ViewModel I create LiveData object
fun getFavoriteServers() {
viewModelScope.launch {
repository.getFavoriteServersToRecyclerView()
.onEach { dataState ->
_favoriteServers.value = dataState
}.launchIn(viewModelScope)
}
}
And everything works fine till the Favorite server is not more available in the Lobby and the Retrofit call failure.
My question is: how to skip the failed call in the loop without crashing whole function.
Emit another flow in catch with emitAll if you wish to continue flow like onResumeNext with RxJava
catch { cause ->
emitAll(flow { emit(DataState.Errorcause)})
}
Ok, I found the solution:
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
val list: MutableList<Server> = mutableListOf()
try {
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
val job = CoroutineScope(coroutineContext).launch {
getFavoritesServersNotLiveData.forEach { fav ->
val server = getServer(fav.ip, fav.port)
server.collect { dataState ->
when (dataState) {
is DataState.Loading -> Log.d(TAG, "loading")
is DataState.Error -> Log.d(TAG, dataState.exception.message!!)
is DataState.Success -> {
list.add(dataState.data)
Log.d(TAG, dataState.data.toString())
}
}
}
}
}
job.join()
emit(DataState.Success(list))
} catch (e: Exception) {
emit(DataState.Error(e))
}
}
when using retrofit you can wrap response object with Response<T> (import response from retrofit) so that,
#GET("v0/server/{ip}/{port}")
suspend fun getServer(
#Path("ip") ip: String,
#Path("port") port: Int
): Response<Server>
and then in the Repository you can check if network failed without using try-catch
suspend fun getFavoriteServersToRecyclerView(): Flow<DataState<List<Server>>> = flow {
emit(DataState.Loading)
val getFavoritesServersNotLiveData = favoritesDao.getFavoritesServersNotLiveData()
if(getFavoritesServersNotLiveData.isSuccessful) {
val list: MutableList<Server> = mutableListOf()
getFavoritesServersNotLiveData.body().forEach { fav ->
val server = soldatApiService.getServer(fav.ip, fav.port)
// if the above request fails it wont go to the else block
list.add(server)
}
emit(DataState.Success(list))
} else {
val error = getFavoritesServersNotLiveData.errorBody()!!
//do something with error
}
}
I am trying to upload multiple image to amazon s3 bucket. The size of the images which i am trying to upload is nearly 300KB each. I am using loop to upload the images. But it's taking more time compared to ios. I am using the below code to upload the images to S3.
val uploadObserver = transferUtility!!.upload(bucketname,
, "img_$timeStamp.jpg", File(fileUri.path!!),
md, CannedAccessControlList.PublicRead)
uploadObserver.setTransferListener(object : TransferListener {
override fun onStateChanged(id: Int, state: TransferState) {
if (TransferState.COMPLETED == state) {
} else if (TransferState.FAILED == state) {
}
}
override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) {
}
override fun onError(id: Int, ex: Exception) {
}
})
}
Please help me, how to increase the speed of upload.
Using RxJava and RxAndroid you can do multiple async task at a time. zip operate binds all task into one task. Sample code for your case is as following:
fun uploadTask(bucketname: String, timeStamp: Long, md: Any, uriList: List<Uri>) {
val singles = mutableListOf<Single<String>>()
uriList.forEach {
singles.add(upload(bucketname, timeStamp, it, md).subscribeOn(Schedulers.io()))
}
val disposable = Single.zip(singles) {
// If all request emits success response, then it will bind all emitted
// object (in this example String) into an Array and you will get callback here.
// You can change your final response here. In this can I am appending all strings
// to a string.
var finalString = ""
it.forEach { responseString ->
finalString += responseString
}
finalString
}
.subscribe({
// you will get final response here.
}, {
// If any one request failed and emits error all task will be stopped and error
// will be thrown here.
it.printStackTrace()
})
}
And upload() method can be as following:
fun upload(bucketname: String, timeStamp: Long, fileUri: Uri, md: Any): Single<String> {
return Single.create<String> { emitter -> // You can change String to anything you want to emmit
val uploadObserver = transferUtility!!.upload(bucketname,
, "img_$timeStamp.jpg", File(fileUri.path!!),
md, CannedAccessControlList.PublicRead)
uploadObserver.setTransferListener(object : TransferListener {
override fun onStateChanged(id: Int, state: TransferState) {
if (TransferState.COMPLETED == state) {
emitter.onSuccess("COMPLETED") // Emmit your object here
} else if (TransferState.FAILED == state) {
emitter.onSuccess("FAILED")
}
}
override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) {
}
override fun onError(id: Int, ex: Exception) {
emitter.onError(ex)
}
})
}
}
Just keep in mind, if any upload request is failed, all other task will be stopped. If you want to continue you have to use onErrorResumeNext() operator in that case.
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())
}
})
I'm doing self-learning of android (mostly), and I'm can't grasp a concept about Testing. I tried googling and youtube but still don't get it, so I really need a sample to test my code below
Could anyone show me how to create unit test request data to server from this code?
fun fetchJSON() {
val url =baseurl + prevnext + idliga
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
val gson = GsonBuilder().create()
val DataMatch= gson.fromJson(body, DataPertandingan::class.java)
runOnUiThread {
rvPrevMatch.adapter= PrevAdapter(DataMatch)
}
}
})
}
And about the instrumented test, how do I test adding something to SQLite.
here is my activity code of adding data to SQLite.
private fun addToFavorite() {
try {
database.use {
insert(
Favorite.DATA_FAVORITE,
Favorite.ID_EVENT to id_event,
Favorite.DATE to tanggaltandingdet.text,
// home team
Favorite.HOME_ID to idhome,
Favorite.HOME_TEAM to timkandangdet.text,
Favorite.HOME_SCORE to skorkandangdet.text,
Favorite.HOME_GOAL_DETAILS to cetakgolkandang.text,
Favorite.HOME_LINEUP_GOALKEEPER to kiperkandang.text,
Favorite.HOME_LINEUP_DEFENSE to bekkandang.text,
Favorite.HOME_LINEUP_MIDFIELD to midkandang.text,
Favorite.HOME_LINEUP_FORWARD to strikerkandang.text,
Favorite.HOME_LINEUP_SUBSTITUTES to cadangankandang.text,
// Favorite.HOME_TEAM_BADGE to urllogokandang.text,
// away team
Favorite.AWAY_ID to idaway,
Favorite.AWAY_TEAM to timtandangdet.text,
Favorite.AWAY_SCORE to skortandangdet.text,
Favorite.AWAY_GOAL_DETAILS to cetakgoltandang.text,
Favorite.AWAY_LINEUP_GOALKEEPER to kipertandang.text,
Favorite.AWAY_LINEUP_DEFENSE to bektandang.text,
Favorite.AWAY_LINEUP_MIDFIELD to midtandang.text,
Favorite.AWAY_LINEUP_FORWARD to strikertandang.text,
Favorite.AWAY_LINEUP_SUBSTITUTES to cadangantandang.text
// Favorite.AWAY_TEAM_BADGE to urllogotandang.text
)
}
toast ("Data Telah Di Simpan" )
} catch (e: SQLiteConstraintException) {
toast("Error: ${e.message}")
}
}