Uploading multiple files with Retrofit 2 to Spring Webflux backend - android

I get the following error when I try to upload multiple files with retrofit 2:
org.springframework.core.codec.DecodingException: Could not find first boundary
When I upload multiple files with postman on the same API endpoint, it works perfectly.
Server controller endpoint:
#PostMapping("{loveSpotId}/photos")
suspend fun uploadToSpot(
#PathVariable loveSpotId: Long,
#RequestPart("photos") filePartFlux: Flux<FilePart>
) {
loveSpotPhotoService.uploadToSpot(loveSpotId, filePartFlux.asFlow())
}
Retrofit API definition:
interface LoveSpotPhotoApi {
#Multipart
#POST("/lovespots/{loveSpotId}/photos")
fun uploadToLoveSpot(
#Path("loveSpotId") loveSpotId: Long,
#Part photos: List<MultipartBody.Part>
): Call<ResponseBody>
// ...
}
Reading photos on Android device:
if (activityResult.resultCode == Activity.RESULT_OK) {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
files.add(File(uri.path!!))
}
loveSpotPhotoService.uploadToLoveSpot(loveSpotId, files, this#LoveSpotDetailsActivity)
}
Client code using Retrofit:
suspend fun uploadToLoveSpot(loveSpotId: Long, photos: List<File>, activity: Activity) {
val loadingBarShower = LoadingBarShower(activity).show()
withContext(Dispatchers.IO) {
val parts: List<MultipartBody.Part> = photos.map { prepareFilePart(it) }
val call = loveSpotPhotoApi.uploadToLoveSpot(loveSpotId, parts)
try {
val response = call.execute()
loadingBarShower.onResponse()
if (response.isSuccessful) {
toaster.showToast(R.string.photo_uploaded_succesfully)
} else {
toaster.showResponseError(response)
}
} catch (e: Exception) {
loadingBarShower.onResponse()
toaster.showToast(R.string.photo_upload_failed)
}
}
}
private fun prepareFilePart(file: File): MultipartBody.Part {
// create RequestBody instance from file
val requestFile = RequestBody.create(
MediaType.get("image/*"),
file
)
// MultipartBody.Part is used to send also the actual file name
return MultipartBody.Part.createFormData("photos", file.name, requestFile)
}
Example headers logged on server when I upload with postman and it works:
[Authorization:"Bearer ...", User-Agent:"PostmanRuntime/7.30.0", Accept:"/", Postman-Token:"7ad875eb-2fe5-40ea-99f0-3ad34c3fa875", Host:"localhost:8090", Accept-Encoding:"gzip, deflate, br", Connection:"keep-alive", Content-Type:"multipart/form-data; boundary=--------------------------877409032202838734061440", content-length:"1555045"]
Example headers logged on server when I upload with retrofit client and it fails:
[Authorization:"Bearer ...", Content-Type:"multipart/form-data; boundary=c6177139-6b31-4d91-b66d-54772a51d963", Host:"192.168.0.143:8090", Connection:"Keep-Alive", Accept-Encoding:"gzip", User-Agent:"okhttp/3.14.9", content-length:"528"]

The problem was that I was not reading photos from the Android device properly. Here is my code that fixed that:
launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
handlePhotoPickerResult(activityResult)
}
fun startPickerIntent(launcher: ActivityResultLauncher<Intent>) {
val intent = Intent()
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_PICK
launcher.launch(intent)
}
private fun handlePhotoPickerResult(activityResult: ActivityResult) {
MainScope().launch {
if (activityResult.resultCode == RESULT_OK) {
val files = PhotoUploadUtils.readResultToFiles(activityResult, contentResolver)
Log.i(this#LoveSpotDetailsActivity::class.simpleName, "Starting upload")
val result: Boolean = if (photoUploadReviewId != null) {
loveSpotPhotoService.uploadToReview(
loveSpotId,
photoUploadReviewId!!,
files,
this#LoveSpotDetailsActivity
)
} else {
loveSpotPhotoService.uploadToLoveSpot(
loveSpotId,
files,
this#LoveSpotDetailsActivity
)
}
photosLoaded = !result
photosRefreshed = !result
photoUploadReviewId = null
if (result) {
Log.i(
this#LoveSpotDetailsActivity::class.simpleName,
"Upload finished, starting refreshing views."
)
startPhotoRefreshSequence()
}
} else {
toaster.showToast(R.string.failed_to_access_photos)
}
}
}
fun readResultToFiles(
activityResult: ActivityResult,
contentResolver: ContentResolver
): List<File> {
val itemCount: Int = activityResult.data?.clipData?.itemCount ?: 0
val files = ArrayList<File>()
for (i in 0 until itemCount) {
val clipData = activityResult.data!!.clipData!!
val uri = clipData.getItemAt(i).uri
Log.i("uri", "$uri")
addToFilesFromUri(uri, files, contentResolver)
}
return files
}
private fun addToFilesFromUri(
uri: Uri,
files: ArrayList<File>,
contentResolver: ContentResolver
) {
val projection = arrayOf(MediaStore.MediaColumns.DATA)
contentResolver.query(uri, projection, null, null, null)
?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
Log.i("columnIndex", "$columnIndex")
val filePath = cursor.getString(columnIndex)
Log.i("filePath", " $filePath")
if (filePath != null) {
val file = File(filePath)
Log.i("file", "$file")
files.add(file)
}
}
}
}
Rest of the code was fine.

Related

Upload image to Graphql server using apollo kotlin

I need to upload image to my Graphql server from android application. The details in the documentation is not working. I need an example.
Came up with the solution. 1st I needed to create an upload scalar type. in Fragment class:
requireContext().contentResolver.openFileDescriptor(
selectedImageUri!!,
"r",
null
) ?: return
val file = File(
requireContext().cacheDir, requireContext().contentResolver.getFileName(
selectedImageUri
)
)
val body = UploadRequestBody(file, "image")
val upload = DefaultUpload.Builder()
.content(file)
.fileName(file.name)
.contentType(body.contentType().toString())
.build()
In case what the UploadRequestBody class does:
class UploadRequestBody(
private val file: File,
private val contentType: String
) : RequestBody() {
override fun contentType() = "$contentType/*".toMediaTypeOrNull()
override fun contentLength() = file.length()
override fun writeTo(sink: BufferedSink) {
val length = file.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val fileInputStream = FileInputStream(file)
var uploaded = 0L
fileInputStream.use { inputStream ->
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
uploaded += read
sink.write(buffer, 0, read)
}
}
}
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
}

Select image and upload to server android

I want to upload the image to server by selecting from gallery
Opening gallery by
val pickPhoto = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startForResult.launch(pickPhoto)
And then activity result
val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
// you will get result here in result.data
if(result.data!=null){
val f=File(result.data.toString())
println(f.absolutePath)
}
}
}
However I think the file is not correct.
My api call is like
#Multipart
#PUT
fun uploadFile(
#Url url:String,
#Part("file") name: RequestBody,
#Part filepart: MultipartBody.Part
): Response<String>
And I am calling this as
val fileToUpload: MultipartBody.Part =
MultipartBody.Part.createFormData("file", file.getName())
val filename: RequestBody =
file.getName().toRequestBody("text/plain".toMediaTypeOrNull())
Api.uploadFile(uploadUrl,filename,fileToUpload)
Its a stream upload
Issue was due to URI only as it was not getting the correct absolute path of file
to retrive the absolute path
private fun getRealPathFromURI(contentURI: Uri): String {
val result: String
val cursor: Cursor? = requireContext().contentResolver.query(contentURI, null, null, null, null)
if (cursor == null) {
result = contentURI.getPath().toString()
} else {
cursor.moveToFirst()
val idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
result = cursor.getString(idx)
cursor.close()
}
return result
}
The activity result
val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
// you will get result here in result.data
if(result.data!=null){
val f=File(getRealPathFromURI(result.data?.data!!))
viewModel.uploadCheque(f)
}
}
}

Upload image from Android to Server that accepts only JPEG and PNG

MediaStore.Images.Media.Data is deprecated so I tried using byte array, but my server throws an error that it accepts only file types of jpeg and png. I have read #commonsware's answers a couple of times from other questions but I can't seem to get it right. How do I get the file type and also the image so it can be attached to my network call? Thanks in advance
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK && requestCode == PROFILE_REQUEST_CODE) {
val uri = data?.data
createImageData(uri)
profile_image.setImageURI(data?.data)
}
}
private fun createImageData(uri: Uri?) {
val inputStream = activity?.contentResolver?.openInputStream(uri!!)
processImage(inputStream)
}
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageData!!)
val image: MultipartBody.Part = MultipartBody.Part.createFormData("image", "user", imageBody)
uploadImage(image)
}
private fun uploadImage(image: MultipartBody.Part) {
val user = appPreferences?.getUser()
val userToken = user?.jwt_token
val token = "Bearer $userToken"
val headers = HashMap<String, String>()
//headers["Authorization"] = token
val uploadResponseCall: Call<ProfileImageResponse> = client.getApi().upload(token, image)
uploadResponseCall.enqueue(object : retrofit2.Callback<ProfileImageResponse> {
override fun onResponse(call: Call<ProfileImageResponse>, response: Response<ProfileImageResponse>) {
val imageResponse = response.body()
val resCode = imageResponse?.statuscode
val msg = imageResponse?.message
if (resCode == 200) {
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, msg!!)
} else {
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, "wrong file type")
}
}
override fun onFailure(call: Call<ProfileImageResponse>, t: Throwable) {
if (t is IOException) {
call.cancel()
Log.d("profilefragment", "issue")
appUtils.showSnackBar(requireActivity().applicationContext, profile_frame, "server error")
}
}
})
}
Here is example
#Multipart
#POST(ApiConstants.SIGNUP)
fun signUp(
#Query("first_name") firstName: String,
#Query("last_name") lastName: String,
#Query("email") email: String,
#Query("password") password: String,
#Query("gender") gender: String,
#Query("phone_code") phoneCode: String,
#Query("phone_no") phoneNo: String,
#Part("id_card_front\"; filename=\"pp.png") id_card_front: RequestBody?
) : Observable<SignUpModel>
Here is usage
var image = RequestBody.create(
MediaType.parse("image/*"),
getFileFromBitmap((imageview.drawable as BitmapDrawable).bitmap, 13)
)
disposable = retroClient.signUp(
fname,
lname,
email,
password,
gender,
phoneCode,
phone,
image,
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result ->
Log.d("response", result.toString())
}, { error ->
Log.e("error", error.toString())
})
the method is used to get file from bitmap
fun getFileFromBitmap(bitmap: Bitmap, index: Int): File {
val f = File(cacheDir, "something+$index")
f.createNewFile()
val bitmap = bitmap
bitmap
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 25 /*ignored for PNG*/, bos)
val bitmapdata = bos.toByteArray()
val fos = FileOutputStream(f)
fos.write(bitmapdata)
fos.flush()
fos.close()
return f
}
Try to add an appropriate Content-Type header. image/png or image/jpg should fit. Also, it possibly, should not be the multipart request. So you'll need such kind of request.
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = imageData!.toRequestBody()
uploadImage(imageBody)
}
or if it's really should be multipart/form-data, you should correctly set mime type for the image part like this:
private fun processImage(inputStream: InputStream?) {
imageData = inputStream?.readBytes()
bitmap = BitmapFactory.decodeStream(inputStream)
val imageBody: RequestBody = RequestBody.create(MediaType.parse("image/png"), imageData!!)
val image: MultipartBody.Part = MultipartBody.Part.createFormData("image", "user", imageBody)
uploadImage(image)
}

I can't find the files I uploaded to Google Drive

I'm trying to upload a file to Google Drive using Google Drive REST API v3. After the upload process is completed, it returns a status code of 200 (successful). But I can't find the files in my Google Drive. Please tell me what am I doing wrong? I will really appreciate if you provide a proper illustration or better still code snippet while helping me with this problem of mine. I am really anticipating your answers.
I have tried following the documentation but I am still getting the same error. I have searched everywhere online and stackoverflow, but none seems to provide the solution to my problem.
here is the code
private val AUTHORIZATION_PARAM = "Authorization"
private val BEARER_VAL = "Bearer "
private val CONTENT_TYPE_PARAM = "Content-Type: "
private val LINE_FEED = "\r\n"
private val APP_FOLDER_ID = "appDataFolder"
fun connectAndStartOperation() {
if (mAuthCode == null) {
signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestProfile()
.requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
.requestIdToken(resources.getString(R.string.gdrive_clientId))
.requestServerAuthCode(resources.getString(R.string.gdrive_clientId))
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, signInOptions!!)
startActivityForResult(mGoogleSignInClient?.signInIntent, CLOUD_STORAGE)
Log.i("mAuthCode", "false")
} else {
Log.i("mAuthCode", "true")
writeDbToDrive()
mNextGoogleApiOperation = INVALID;
}
}
fun disconnect() {
mGoogleSignInClient?.signOut()
mActivity = null
mNextGoogleApiOperation = INVALID
mAuthCode = null
mAccessToken = null
mTokenExpired = 0
}
override fun onDestroy() {
disconnect()
super.onDestroy()
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CLOUD_STORAGE) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
.addOnSuccessListener(this)
.addOnFailureListener(this)
}
}
override fun onSuccess(googleSignInAccount: GoogleSignInAccount?) {
Log.i("mAuthCode", "Success")
val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
idTokenString = googleSignInAccount?.idToken
mAuthCode = googleSignInAccount?.serverAuthCode
mGoogleSignInAccount = googleSignInAccount
doAsync {
try {
mAccessToken = GoogleAuthUtil.getToken(this#SettingsActivity, mGoogleSignInAccount?.account, scope)
} catch (e: Exception) {
Log.i("Error AccessToken", "${e.message}")
e.printStackTrace()
}
uiThread {
Log.i("AccessTokenMy", "$mAccessToken")
}
}
}
override fun onFailure(p0: java.lang.Exception) {
Log.i("mAuthCode", "Failed")
p0.printStackTrace()
}
private fun writeDbToDrive() {
var conn: HttpURLConnection? = null
var os: OutputStream? = null
val accessToken = requestAccessToken(mGoogleSignInAccount!!)
if (accessToken == null)
return
try {
val boundary = "pb" + System.currentTimeMillis()
val url = URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart")
conn = url.openConnection() as HttpURLConnection
conn.requestMethod = "POST"
conn.useCaches = false
conn.doOutput = true
conn.doInput = true
conn.connectTimeout = 5000
conn.setRequestProperty(AUTHORIZATION_PARAM, BEARER_VAL + accessToken!!)
conn.setRequestProperty("Content-Type", "multipart/related; boundary=$boundary")
Log.i("Action", "Parameter set for server")
/////// Prepare data
//val timestamp = SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.getDefault()).format(Date())
// Prepare file metadata (Change your backup file name here)
val b = StringBuilder()
b.append('{')
.append("\"name\":\"").append(exportedFileName).append('\"')
.append(',')
.append("\"mimeType\":").append("\"text\\/csv\"")
.append(',') //"\"application\\/vnd.google-apps.unknown\""
.append("\"parents\":").append("[\"").append(APP_FOLDER_ID).append("\"]")
.append('}')
val metadata = b.toString()
val data = readFile(File(filePath))
/////// Calculate body length
var bodyLength = 0
// MetaData part
b.setLength(0)
b.append("--").append(boundary).append(LINE_FEED)
b.append(CONTENT_TYPE_PARAM).append("application/json; charset=UTF-8")
.append(LINE_FEED)
b.append(LINE_FEED)
b.append(metadata).append(LINE_FEED)
b.append(LINE_FEED)
b.append("--").append(boundary).append(LINE_FEED)
b.append(CONTENT_TYPE_PARAM).append("text/csv").append(LINE_FEED)
b.append(LINE_FEED)
val beforeFilePart = b.toString().toByteArray(charset("UTF_8"))
bodyLength += beforeFilePart.size
bodyLength += data.size // File
b.setLength(0)
b.append(LINE_FEED)
b.append("--").append(boundary).append("--")
val afterFilePart = b.toString().toByteArray(charset("UTF_8"))
bodyLength += afterFilePart.size
conn.setRequestProperty("Content-Length", bodyLength.toString())
//if (BuildConfig.DEBUG_MODE) DebugHelper.log("LENGTH", bodyLength)
/////// Write to socket
os = conn.outputStream
try {
os!!.write(beforeFilePart)
os!!.write(data)
os!!.write(afterFilePart)
os!!.flush()
} catch (e: Exception) {
e.printStackTrace()
}
val msg = conn.responseMessage
val code = conn.responseCode
if (code == 200) {
Log.i("writeDbToDrive", "Exported Successfully: $code $msg")
} else {
Log.i("writeDbToDrive", "Error: $code $msg")
}
} catch (e: Exception) {
e.printStackTrace()
Log.i("writeDbToDrive", e.message!!)
} finally {
if (os != null) {
try {
os!!.close()
} catch (e: IOException) {
}
}
conn?.disconnect()
}
}
#Throws(IOException::class)
private fun readFile(file: File): ByteArray {
val f = RandomAccessFile(file, "r")
try {
val longlength = f.length()
val length = longlength.toInt()
if (length.toLong() != longlength)
throw IOException("File size >= 10 Mb")
val data = ByteArray(length)
f.readFully(data)
return data
} finally {
f.close()
}
}
private fun requestAccessToken(mGoogleSignInAccount: GoogleSignInAccount): String? {
val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
doAsync {
try {
mAccessToken = GoogleAuthUtil.getToken(this#SettingsActivity, mGoogleSignInAccount?.account, scope)
} catch (e: Exception) {
Log.i("Error AccessToken", "${e.message}")
e.printStackTrace()
}
uiThread {
Log.i("AccessTokenMy", "$mAccessToken")
}
}
return mAccessToken
}
After reading through this Files: create Documentation, I have finally fixed the problem. Unknown to me is that the files where being saved in the AppData folder created by my app. The AppData folder is hidden which can only be accessible by and through my app. For me to be able to save the file to My Drive folder, I removed the part of the metadata
` .append(',')
.append("\"parents\":").append("[\"").append(APP_FOLDER_ID).append("\"]")`
So the metadata part is now like this
val b = StringBuilder()
b.append('{')
.append("\"name\":\"").append(exportedFileName).append('\"')
.append(',')
.append("\"mimeType\":").append("\"text\\/csv\"")
.append('}')
val metadata = b.toString()
Every other thing remains the same

Second WorkRequest of WorkContinuation do not work

I am trying to start two work request, one worker sends a request to the server for generating excel file and obtains URL for download. Another work starts after previous and must to download that file. First work starts and returns Result.SUCCESS.
Problem is another WorkRequest just not execute. LoadInvoiceFileWorker do nothing.
What I need to do or what I do wrong?
Here is my code:
InvoiceDetailsViewModel:
class InvoiceDetailsViewModel : ViewModel() {
private val mWorkManager: WorkManager = WorkManager.getInstance()
fun generateAndLoadExcel(invoiceId: Int, invoiceName: String, enterpriseId: Int) {
val genInvoiceWorkerBuilder = OneTimeWorkRequest.Builder(GenerateExcelWorker::class.java)
genInvoiceWorkerBuilder.setInputData(createInputDataForGenerateExcel(invoiceId, invoiceName, enterpriseId))
val constraintBuilder = Constraints.Builder()
//constraintBuilder.setRequiredNetworkType(NetworkType.CONNECTED)
genInvoiceWorkerBuilder.setConstraints(constraintBuilder.build())
val continuation = mWorkManager.beginWith(
genInvoiceWorkerBuilder.build()
)
val loadFileWorkerBuilder = OneTimeWorkRequest.Builder(LoadInvoiceFileWorker::class.java)
//loadFileWorkerBuilder.setConstraints(Constraints.NONE)
continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
}
private fun createInputDataForGenerateExcel(invoiceId: Int, invoiceName: String, enterpriseId: Int): Data {
val builder = Data.Builder()
builder.putInt(WorkerConstants.INVOICE_ID, invoiceId)
builder.putString(WorkerConstants.INVOICE_NAME, invoiceName)
builder.putInt(WorkerConstants.ENTERPRISE_ID, enterpriseId)
return builder.build()
}
}
GenerateExcelWorker:
class GenerateExcelWorker : Worker() {
companion object {
private val TAG = GenerateExcelWorker::class.java.simpleName
}
override fun doWork(): Result {
val appCont = applicationContext
val tokenType = PreferenceUtil.getString(TOKEN_TYPE, appCont, R.string.shared_pref_name)
val accessToken = PreferenceUtil.getString(ACCESS_TOKEN, appCont, R.string.shared_pref_name)
val enterpriseId = inputData.getInt(WorkerConstants.ENTERPRISE_ID, 0)
val invoiceId = inputData.getInt(WorkerConstants.INVOICE_ID, 0)
val invoiceName = inputData.getString(WorkerConstants.INVOICE_NAME)
makeStatusNotification(applicationContext, invoiceId, invoiceName
?: ("Invoice ${invoiceId.str()}"))
try {
val rd = RequestData()
rd.putValue("authorization", "$tokenType $accessToken", RequestData.TYPE_HEADER)
rd.putValue(FTUrls.SendingParameters.ENTERPRISE_ID, enterpriseId, RequestData.TYPE_PATH)
rd.putValue(FTUrls.SendingParameters.INVOICE_ID, invoiceId, RequestData.TYPE_PATH)
val excelUrl = InvoiceManager().generateIncomeInvoiceExcel(rd)
outputData = Data.Builder().putString(WorkerConstants.FILE_URL, excelUrl).build()
return Result.SUCCESS
} catch (t: Throwable) {
Log.e(TAG, "Error generating excel file for invoice $invoiceName ($invoiceId)", t)
if (t is UnauthenticatedException) {
outputData = Data.Builder().putBoolean(WorkerConstants.FILE_URL, true).build()
} else {
ExceptionLogger.logException(t)
Toast.makeText(applicationContext, t.message, Toast.LENGTH_SHORT).show()
}
return Result.FAILURE
}
}
private fun makeStatusNotification(context: Context, invoiceId: Int, invoiceTitle: String) {
// Make a channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val name = WorkerConstants.NOTIFICATION_CHANNEL_NAME
val description = WorkerConstants.NOTIFICATION_CHANNEL_DESCRIPTION
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(WorkerConstants.CHANNEL_ID, name, importance)
channel.description = description
// Add the channel
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(context, WorkerConstants.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_autorenew_blue)
.setContentTitle(WorkerConstants.NOTIFICATION_TITLE)
.setContentText(String.format(WorkerConstants.NOTIFICATION_TEXT, invoiceTitle))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVibrate(LongArray(0))
NotificationManagerCompat.from(context).notify(invoiceId, builder.build())
}
}
LoadInvoiceFileWorker:
class LoadInvoiceFileWorker : Worker() {
companion object {
private val TAG = LoadInvoiceFileWorker::class.java.simpleName
}
override fun doWork(): Result {
try {
val fileUrl = inputData.getString(WorkerConstants.FILE_URL)
val invoiceId = inputData.getInt(WorkerConstants.INVOICE_ID, 0)
val invoiceName = inputData.getString(WorkerConstants.INVOICE_NAME)
val r = DownloadManager.Request(Uri.parse(fileUrl))
// This put the download in the same Download dir the browser uses
r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, invoiceName
?: ("Invoice ${invoiceId.str()}"))
// Notify user when download is completed
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// Start download
val dm = applicationContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager?
if (dm != null) {
dm.enqueue(r)
} else {
Log.w(TAG, "Download manager not exists for load invoice excel file")
ToastError(applicationContext, R.string.download_manager_not_found, Toast.LENGTH_SHORT)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(fileUrl))
try {
applicationContext.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "Error open browser for view invoice excel file", e)
ToastError(applicationContext, R.string.browser_not_found, Toast.LENGTH_SHORT)
}
}
clearGenerateFileNotification(invoiceId)
return Result.SUCCESS
} catch (t: Throwable) {
Log.e(TAG, "Error loading excel generated file", t)
ExceptionLogger.logException(t)
ToastError(applicationContext, R.string.error_during_loading_file, Toast.LENGTH_SHORT)
return Result.FAILURE
}
}
private fun clearGenerateFileNotification(invoiceId: Int) {
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(invoiceId)
}
}
WorkerConstants:
object WorkerConstants {
const val ENTERPRISE_ID = "enterprise_id"
const val INVOICE_ID = "invoice_id"
const val INVOICE_NAME = "invoice_name"
const val FILE_URL = "file_url"
const val UNIQUE_WORK_NAME_FOR_INVOICE = "generate_and_load_excel_for_invoice"
const val NOTIFICATION_CHANNEL_NAME = "GenerateExcelWorker Notifications"
const val NOTIFICATION_CHANNEL_DESCRIPTION = "Shows notifications whenever work starts"
const val NOTIFICATION_TITLE = "Генерація ексель файла"
const val NOTIFICATION_TEXT = "Генерація ексель файла накладної %s"
const val CHANNEL_ID = "GENERATE_INVOICE_NOTIFICATION"
}
Ok, I found my mistake. Instead of this:
...
continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
I need make this:
...
continuation = continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
I was apllying enqueue() for first continuation of one request. Method WorkContinuation.then() returns new object which contains old continuation with new added request.

Categories

Resources