Store full-size images using a FileProvider - kotlin

I want to save an image in its full size. I use a file provider for this. I can take the picture, but I don't get any data in the onActivityResult function. The intent is null.
What am I doing wrong?
provider_path.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="." />
</paths>
provider in AndroidManifest.xml:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.bkoubik.longlesstime.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_path"/>
</provider>
My Fragment Class:
fun capturePhoto(){
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val fileUri:File = createImageFile();
val photoURI = FileProvider.getUriForFile(
requireActivity(),
"com.bkoubik.longlesstime.fileprovider",
fileUri
)
intent.putExtra( MediaStore.EXTRA_OUTPUT, photoURI )
startActivityForResult(intent,IMAGE_REQUEST)
}
private fun createImageFile(): File {
val wrapper = ContextWrapper(requireContext())
var photoFile = wrapper.getDir(IMAGE_DIRECTORY,Context.MODE_PRIVATE)
photoFile = File(photoFile,"${UUID.randomUUID()}.jpg")
return photoFile
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null)
{
//data = null
val imgData = data.data!!
...
}
}
companion object {
private val IMAGE_DIRECTORY = "long_less"
}
Error:
java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.bkoubik.longlesstime/app_long_less/1219a0bd-c17e-4709-b880-1e4f549362f6.jpg

First of all, I'll strongly recommend you to use new contracts API which is also recommended by Google:
While the underlying startActivityForResult() and onActivityResult() APIs are available on the Activity class on all API levels, it is strongly recommended to use the Activity Result APIs introduced in AndroidX Activity and Fragment. (see Android docs)
Than, when photo was taken from GUI, I'm getting photo data from provider like this (it's my separate camera request Activity). Also, I'm using external cache directory because on my Android 10.0 device data folder is not accessible (see Android docs)
P.S. Take a note about grantUriPermission method call on photo URI
// URI for photo file
private lateinit var mPhotoUri : Uri
/// Photo file itself
private lateinit var mPhotoFile : File
// Camera permission request
private val mRequestCamera = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
// Camera permission request parse
when (result.resultCode) {
// Success
RESULT_OK -> {
// Photo request
mRequestPhoto.launch(mPhotoUri)
}
// No permisison
else -> {
// Close activity
finish()
}
}
}
// Photo request
private val mRequestPhoto = registerForActivityResult(ActivityResultContracts.TakePicture()) { result ->
// Check request result
when (result) {
// Success
true -> {
// Photo from media stream
val photoData = BitmapFactory.decodeStream(
contentResolver.openInputStream(mPhotoUri)
)
// Stream for photo save
val dataStream = ByteArrayOutputStream()
// Compress photo as JPEG of 90% quality
photoData.compress(
Bitmap.CompressFormat.JPEG,
90,
dataStream
)
// Coroutine to write photo
CoroutineScope(Dispatchers.Main).launch {
// Inside IO context
withContext(Dispatchers.IO) {
// File output stream
FileOutputStream(mPhotoFile).also { fileStream ->
// Write JPEG image data to file
fileStream.write(dataStream.toByteArray())
// Close JPEG image data stream
dataStream.close()
// Close file stream
fileStream.close()
}
}
}
// Done here - notify someone photo is ready
sendBroadcast(
Intent(
<Where/Whom to send>
).apply {
// Content provider URI
putExtra(
<Photo URI extra name>,
mPhotoUri
)
}
)
// Close activity
finish()
}
// Error
else -> {
// Close activity
finish()
}
}
}
// Activity onCreate method
#Override
override fun onCreate(savedInstanceState: Bundle?) {
// Parent method call
super.onCreate(savedInstanceState)
// Try
try {
// Create temporary file
mPhotoFile = File.createTempFile(
"<file prefix>",
"<file suffix>",
externalCacheDir
).apply {
// Make it writable
setWritable(true)
}
// Get file URI (Android M+ ?)
mPhotoUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Trying to get provider (I send it to this Activity via Intent with extra "EXTRA_AUTHORITY")
intent?.extras?.getString("EXTRA_AUTHORITY")
?.let { authority ->
// For Android 7.0+ getting FileProvider
FileProvider.getUriForFile(
applicationContext,
authority,
mPhotoFile
)
// Else for Android 7.0 or lower
} ?: Uri.fromFile(mPhotoFile)
} else {
// Else for Android 7.0 or lower
Uri.fromFile(mPhotoFile)
}
// Permissions for URI itself
grantUriPermission(
packageName,
mPhotoUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
)
// Catch
} catch (e: Exception) {
// Stacktrace to logcat
e.printStackTrace()
// Close camera activity
finish()
}
// No camera permissions ?
if (!ActivityPermissionsCamera.hasPermissions(applicationContext)) {
// Intent for camera permissions
val permissionsIntent = Intent(
applicationContext,
<Permissions for Camera Activity>::class.java
).apply {
// New task
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
// Request permissions
mRequestCamera.launch(permissionsIntent)
} else {
// Camera launch
mRequestPhoto.launch(mPhotoUri)
}
}
And this is how I reques photo from camera:
// Photo pending intent
private fun createPendingIntentPhoto() : PendingIntent? {
// Runtime check - any camera ?!
if (!applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
// No camera - no photo !!!
return null
}
// Intent flags
val intentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or
PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
// Pending intent
return PendingIntent.getActivity(
applicationContext,
1,
Intent(
applicationContext,
<Activity for Camera>::class.java
).apply {
// Pass provider as Intent extra
putExtra("EXTRA_AUTHORITY", "${BuildConfig.APPLICATION_ID}.provider")
},
intentFlags
)
}
P.P.S. Quick note on why do I pass provider URI over Intent's extra - my camera routines are in separate library which is used in GUI-project So they can have different BuildConfig.APPLICATION_ID. This allows me to use same library in many projects.

Related

Application Crashes Accepting Photo In Landscape Mode

I've got an activity which uses camera intent. If I take a picture vertically and accept to display it in an imageview vertically there is no problem. If i take a picture horizontally and accept it while having the mobile horizontally it crashes, but if I turn it to vertical it works. Does anybody know why this might be happening?
It does not have much sense cause when the app crashes the only thing it says is that the photoFile is null, it seems as if there was no picture but in fact there is a picture.
Here is the code from the activity:
private const val FILE_NAME = "photo.jpg"
class TextCameraActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
private lateinit var binding: ActivityTextCameraBinding
private lateinit var bitmap : Bitmap
private lateinit var photoFile: File
private var tts: TextToSpeech? = null
private var locale : Locale = Locale("es", "ES")
private var progressBar : ProgressBar? = null
private var i = 0
private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTextCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btn7.setOnClickListener {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
photoFile = getPhotoFile(FILE_NAME)
val fileProvider = FileProvider.getUriForFile(this, "com.example.fileprovider", photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileProvider)
if(takePictureIntent.resolveActivity(this.packageManager) != null) {
startActivityForResult(takePictureIntent, 42)
} else {
Toast.makeText(this, "Unable to open camera", Toast.LENGTH_SHORT).show()
Log.d("mensaje", "?????Unable to open camera")
}
}
tts = TextToSpeech(this, this)
progressBar = binding.progressBar
binding.btnReconocerImagen.setOnClickListener {
progressBar!!.visibility = View.VISIBLE
i = progressBar!!.progress
Thread(Runnable {
while (i < 10) {
i += 1
handler.post(Runnable {
progressBar!!.progress = i
})
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
progressBar!!.visibility = View.INVISIBLE
}).start()
recognizeText()
}
val actionBar = supportActionBar
actionBar?.setDisplayHomeAsUpEnabled(true)
}
private fun getPhotoFile(fileName:String):File {
val storageDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(fileName, ".jpg", storageDirectory)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return true
}
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
// set US English as language for tts
val result = tts!!.setLanguage(locale)
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS","The Language specified is not supported!")
} else {
binding.btnReconocerImagen.isEnabled = true
}
} else {
Log.e("TTS", "Initilization Failed!")
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
binding.imageView.setImageBitmap(bitmap)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun recognizeText() {
try {
val image = InputImage.fromBitmap(bitmap, 0)
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val result = recognizer.process(image)
.addOnSuccessListener { visionText ->
for (block in visionText.textBlocks) {
val boundingBox = block.boundingBox
val cornerPoints = block.cornerPoints
val text = block.text
Log.d("mensaje", "he encontrado $text")
binding.tvTextoReconocido.text = "El texto reconocido es: $text"
tts!!.speak("El texto reconocido es $text", TextToSpeech.QUEUE_FLUSH, null, "")
for (line in block.lines) {
// ...
for (element in line.elements) {
// ...
}
}
}
}
.addOnFailureListener { e ->
}
} catch (e : Exception) {
Log.d("mensaje", "NO HAY IMAGEN SELECCIONADA")
Toast.makeText(this,"NO HAS SELECCIONADO NINGUNA IMAGEN PARA RECONOCER", Toast.LENGTH_SHORT).show()
}
}
}
Error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.appdigitalinkrecognition, PID: 30407
java.lang.RuntimeException: Unable to resume activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=42, result=-1, data=null} to activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4918)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=42, result=-1, data=null} to activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at android.app.ActivityThread.deliverResults(ActivityThread.java:5590)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4905)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955) 
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52) 
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:246) 
at android.app.ActivityThread.main(ActivityThread.java:8653) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130) 
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at com.example.appdigitalinkrecognition.TextCameraActivity.onActivityResult(TextCameraActivity.kt:143)
at android.app.Activity.dispatchActivityResult(Activity.java:8550)
at android.app.ActivityThread.deliverResults(ActivityThread.java:5583)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4905) 
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955) 
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52) 
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:246) 
at android.app.ActivityThread.main(ActivityThread.java:8653) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
```` 
If I had to guess, you're getting different results because changing the orientation destroys an Activity, so a new one can be created to match the new orientation. So maybe that's happening in the background - but if you take a photo vertically, there's no orientation change so the Activity isn't destroyed
That's an issue because like the docs say:
Note: Since your process and activity can be destroyed between when you call launch() and when the onActivityResult() callback is triggered, any additional state needed to handle the result must be saved and restored separately from these APIs.
This is the closest thing I can find to them explicitly saying "onActivityResult can run before onCreate", but if that's what's happening to you, then that's why you're getting that lateinit property photoFile has not been initialized error.
It might also be because you're not actually initialising it in onCreate, you're doing it in a click listener - so even if onCreate does run first, the button would need to be clicked again, and onActivityResult definitely runs before that. But just moving it into onCreate might not be enough
I think to handle this properly, you'll need to think about your "show the photo" task having two parts - creating the path (in onCreate) and getting a result (through onActivityResult). You can't guarantee the order of those two things, so when you do one, you'll have to check if the other part has already been completed - if it has, you can show the image.
You could do something like this:
private var pathInitialised = false
private var photoTaken = false
override fun onCreate(savedInstanceState: Bundle?) {
...
// don't do this in the click listener - it needs to be available when the activity starts
photoFile = getPhotoFile(FILE_NAME)
// set the flag, and try to show the pic if ready
pathInitialised = true
tryDisplayPhoto()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
// set the flag, and try to show the pic if ready
photoTaken = true
tryDisplayPhoto()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun tryDisplayPhoto() {
if (pathInitialised && photoTaken) {
// now you know that the path is available and a photo needs to be shown
bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
binding.imageView.setImageBitmap(bitmap)
}
}
So now, no matter what order those two methods are called, they both try to display a pic - and that will only happen when the last of those methods is called, and all the pieces are in place
Declaring photoFile as optional should solve the problem:
private var photoFile: File? = null
You will need to add a null-check in your onActivityResult:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
photoFile?.let { photo ->
bitmap = BitmapFactory.decodeFile(photo.absolutePath)
binding.imageView.setImageBitmap(bitmap)
} ?: run {
// Photo is not defined...
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}

Kotlin: Is it possible to make a function, which calls a retrofit service, to return a String value?

I have a Fragment and a View Model.
The layout of the Fragment contains a button.
When the button is clicked, we try to get an API response, which contains a url.
That url is used to start an intent to open a web page.
I am currently accomplishing this with event driven programming.
The button in the Fragment is clicked.
The function in the view model is called to get the API response, which contains the url.
The url in the view model is assigned as live data, which is observed in the fragment.
The fragment observes the url live data has changed. It attempts to launch the WebView with the new url.
Can the Fragment skip Observing for the url and directly get the ViewModel function to return a string?
Here is the code for the Fragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Set the OnClickListener
myButton.setOnClickListener {
myViewModel.getUrlQueryResults()
}
// Observables to open WebView from Url
myViewModel.myUrl.observe(viewLifecycleOwner, Observer {
it?.let{
if (it.isEmpty()) {
// No Url found in this API response
}
else {
// Open the WebView
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))
startActivity(intent)
}
catch (e: Exception) {
// Log the catch statement
}
}
}
})
}
Here is the code for the ViewModel:
// Live data observed in fragment. When this changes, fragment will attempt to launch Website with the url
private val _myUrl = MutableLiveData<String>()
val myUrl: LiveData<String>
get() = _myUrl
// Possible to make this return a string?
fun getUrlQueryResults() {
InfoQueryApi.retrofitService.getInfo(apiKey).enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
try {
// Store the response here
apiResponse = parseInfoJsonResult(JSONObject(response.body()!!))
// Grab the url from the response
var urlFromResponse = apiResponse?.url
if (urlFromResponse.isNullOrEmpty()) {
urlFromResponse = ""
}
// Store the urlFromResponse in the live data so Fragment can Observe and act when the value changes
_myUrl.value = urlFromResponse
} catch (e: Exception) {
// Log catch statement
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
// Log error
}
})
}

Something wrong with multiple Global scopes

I've an app that is have 3 GlobalScopes:
First reading a stream from url, and return InputStream
Second, start after the completion of the first one, save the InputStream in the device, and return the saved file uri
Third, start after the completion of the second one, and do some processing with the file
I've something wrong in the second scope, as the file is not saved, my full code is below:
MainActivity
package com.example.dl
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.dl.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import java.io.File
import java.io.InputStream
import java.net.URL
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// setContentView(R.layout.activity_main)
val context = this
val urlFile:URL = URL( "https://drive.google.com/uc?export=download&id="+
"1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")
// show image url in text view
binding.tvDownload.text = urlFile.toString()
val tag = "Main Activity"
Log.i(tag, "Trying t get stream")
binding.button.setOnClickListener {
it.isEnabled = false // disable button
binding.progressBar.visibility = View.VISIBLE
// GlobalScope 1
// async task to get / download bitmap from url
val result: Deferred<InputStream?> = GlobalScope.async {
urlFile.toStream(context)
}
// GlobalScope 2
val saved: Deferred<Uri?> = GlobalScope.async {
// get the downloaded bitmap
val fileStream : InputStream? = result.await()
// if downloaded then saved it to internal storage
Log.i(tag, "Stream collected, trying to save it") // <--- This is printing on the LogCat
fileStream?.saveToInternalStorage(context) // <-- This looks not to be executed!
}
// GlobalScope 3
GlobalScope.launch(Dispatchers.Main) {
val savedUri : Uri? = saved.await() // <-- This looks not to be executed!
Log.i(tag, "Stream saved")
val execFile = File(savedUri.toString())
Log.i(tag, "Setting file executable")
// execFile?.setExecutable(true)
Log.i(tag, "Running executable file")
// Runtime.getRuntime().exec(savedUri.toString())
// display saved bitmap to image view from internal storage
binding.imageView.setImageURI(savedUri)
// show bitmap saved uri in text view
binding.tvSaved.text = savedUri.toString()
it.isEnabled = true // enable button
binding.progressBar.visibility = View.INVISIBLE
}
}
}
}
The function that is running in the first scope is:
package com.example.dl
import android.content.Context
import android.util.Log
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
// extension function to get / download bitmap from url
fun URL.toStream(context : Context): InputStream? {
return try {
val tag = "Getting stream"
Log.i(tag, "Reading the stream from the web")
//this is the name of the local file you will create
val u = URL(this.toString())
val c = u.openConnection() as HttpURLConnection
c.requestMethod = "GET"
c.doOutput = true
c.connect()
c.inputStream
} catch (e: IOException){
null
}
}
The function that is running in the second scope, whihc looks to be no reached or not working properly, is:
package com.example.dl
import android.content.Context
import android.content.ContextWrapper
import android.net.Uri
import android.util.Log
import android.widget.Toast
import java.io.*
// extension function to save an image to internal storage
fun InputStream.saveToInternalStorage(context: Context): Uri?{
val tag = "Saving stream"
Log.i(tag, "Saving the stream from the web")
val targetFileName: String? = "server"
val wrapper = ContextWrapper(context)
var file = wrapper.getDir("images", Context.MODE_PRIVATE)
// create a file to save the downloaded one
file = File(file, targetFileName)
// get the file output stream
val stream: OutputStream = FileOutputStream(file)
Toast.makeText(context, "downloading", Toast.LENGTH_LONG).show()
var len1 = 0
return try {
// this.copyTo(stream)
var size: Long = 0
val buffer = ByteArray(1024)
Log.i(tag, "stream size ${this.readBytes().size}")
while (this.read(buffer).also { len1 = it } > 0) {
stream.write(buffer, 0, len1)
size += len1;
Log.i(tag, "file saved $size")
}
// flush the stream
stream.flush()
// close stream
stream.close()
this.close()
Log.i(tag, "file saved")
// compress bitmap
// compress(Bitmap.CompressFormat.JPEG, 100, stream)
// return the saved image uri
Uri.parse(file.absolutePath)
} catch (e: IOException){ // catch the exception
e.printStackTrace()
null
}
}
UPDATE
Updated my code based on the commint given about GlobalScope, still getting same result
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// setContentView(R.layout.activity_main)
val context = this
val urlFile:URL = URL( "https://drive.google.com/uc?export=download&id="+
"1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")
// show image url in text view
binding.tvDownload.text = urlFile.toString()
binding.button.setOnClickListener {
it.isEnabled = false // disable button
binding.progressBar.visibility = View.VISIBLE
// DownloadExe(context)
val tag = "Main Activity"
Log.i(tag, "Trying t get stream")
runBlocking {
Log.i(tag, "Collect stream")
val download = async(context = Dispatchers.IO) { urlFile.toStream(context) }
val fileStream : InputStream? = download.await()
Log.i(tag, "Stream collected, trying to save it")
val save = async(context = Dispatchers.IO) {
fileStream?.saveToInternalStorage(context) // <-- Not working
}
val savedUri : Uri? = save.await()
Log.i(tag, "Stream saved, trying to get path")
val execFile = File(savedUri.toString())
Log.i(tag, "Setting file executable")
// execFile?.setExecutable(true)
Log.i(tag, "Running executable file")
// Runtime.getRuntime().exec(savedUri.toString())
// display saved bitmap to image view from internal storage
// binding.imageView.setImageURI(savedUri)
// show bitmap saved uri in text view
binding.tvSaved.text = savedUri.toString()
it.isEnabled = true // enable button
binding.progressBar.visibility = View.INVISIBLE
}
}
}
}
I got in the catlog:
I/Choreographer: Skipped 632 frames! The application may be doing too much work on its main thread.
So, I changed my code to:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// setContentView(R.layout.activity_main)
val context = this
val urlFile:URL = URL( "https://drive.google.com/uc?export=download&id="+
"1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")
// show image url in text view
binding.tvDownload.text = urlFile.toString()
binding.button.setOnClickListener {
it.isEnabled = false // disable button
binding.progressBar.visibility = View.VISIBLE
// DownloadExe(context)
val tag = "Main Activity"
Log.i(tag, "Trying t get stream")
var savedUri : Uri?
object : Thread() {
override fun run() {
runBlocking {
Log.i(tag, "Collect stream")
val download = async(context = Dispatchers.IO) { urlFile.toStream(context) }
val fileStream : InputStream? = download.await()
Log.i(tag, "Stream collected, trying to save it")
val save = async(context = Dispatchers.IO) {
fileStream?.saveToInternalStorage(context) // <-- Not working
}
savedUri = save.await()
Log.i(tag, "Stream saved, trying to get path")
val execFile = File(savedUri.toString())
Log.i(tag, "Setting file executable")
// execFile?.setExecutable(true)
Log.i(tag, "Running executable file")
// Runtime.getRuntime().exec(savedUri.toString())
// display saved bitmap to image view from internal storage
// binding.imageView.setImageURI(savedUri)
}
try {
// code runs in a thread
runOnUiThread {
// show bitmap saved uri in text view
binding.tvSaved.text = savedUri.toString()
it.isEnabled = true // enable button
binding.progressBar.visibility = View.INVISIBLE
}
} catch (ex: Exception) {
Log.i("---", "Exception in thread")
}
}
}.start()
}
}
}
But still getting the same, any my cat og is:
I/Main Activity: Trying t get stream
I/Main Activity: Collect stream
I/Getting stream: Reading the stream from the web
I/Main Activity: Stream collected, trying to save it
I/Main Activity: Stream saved, trying to get path
Setting file executable
Running executable file
W/BpBinder: Slow Binder: BpBinder transact took 227ms, interface=android.view.IWindowSession, code=5 oneway=false
W/System: A resource failed to call end.
This doesn't really solve your specific issue concretely, but I wanted to share how your use of coroutines could be cleaner, which might help track down the problem. As I mentioned in the comment, I think your URL.toStream extension function is likely returning null, so that would be the place to start with your debugging.
You don't need three separate coroutines because you have a sequence of tasks that you want to run in order. So they can be in a single coroutine. One of the main benefits of coroutines is that you can run sequential asynchronous code using a syntax that looks synchronous.
You should use lifecycleScope to run your coroutine, add it as.
dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
}
One of the main points of a CoroutineScope is to provide a way to easily cancel all coroutines associated with some lifecycle of your application flow. In this case, you would want to cancel the coroutine when the Activity or Fragment goes away, so the pre-existing lifecycleScope is the natural choice. It is set up by the Android framework to automatically cancel all the coroutines it has launched if the Activity is destroyed while they are still running.
It should be extremely rare to ever use runBlocking, since it blocks the current thread while the coroutine is running, defeating the purpose of using coroutines. It's main purpose is in a JVM app's main function to make the whole app wait for all the coroutines to finish before shutting down. Or it can be used for unit tests. The only reason I can think of for it to ever appear in an Android application is if you have a module that is using Java 8 concurrency that you don't want to migrate to coroutines, but it's calling suspend functions from a library from a background thread.
lifecycleScope runs on the Main dispatcher. You only have to wrap blocking calls in withContext.
binding.button.setOnClickListener { button ->
lifecycleScope.launch {
button.isEnabled = false // disable button
binding.progressBar.visibility = View.VISIBLE
// perform these tasks off the main thread
val savedUri : Uri? = withContext(Dispatchers.IO) {
// get / download bitmap from url
val fileStream : InputStream? = urlFile.toStream(context)
Log.i(tag, "Stream $fileStream collected, trying to save it") // I put the reference in your log so if it is null you'll see.
fileStream?.saveToInternalStorage(context)
}
// You need to check for null. Otherwise savedUri.toString() gives you the String "null"
if (savedUri == null) {
Log.e(tag, "URI was null. Nothing to save")
return#launch
}
val execFile = File(savedUri.toString())
Log.i(tag, "Stream saved")
// Log.i(tag, "Setting file executable")
// execFile?.setExecutable(true)
// Log.i(tag, "Running executable file")
// Runtime.getRuntime().exec(savedUri.toString())
// display saved bitmap to image view from internal storage
binding.imageView.setImageURI(savedUri)
// show bitmap saved uri in text view
binding.tvSaved.text = savedUri.toString()
button.isEnabled = true // enable button
binding.progressBar.visibility = View.INVISIBLE
}
}

How can I ensure Kotlin Coroutines are finishing in the correct order?

I am attempting to build a library that allows an app to download a json file I provide, and then based on its contents, download images from the web. I have implemented it thus far with Kotlin Coroutines along with Ktor, but I have an issue which is evading my grasp of what to do.
This is the data class I am using to define each image:
data class ListImage(val name: String, val url: String)
The user calls an init function which downloads the new json file. Once that file is downloaded, the app needs to download a number of images as defined by the file using getImages. Then a list is populated using the data class and an adapter.
Here is the code I am using to fetch the file:
fun init(context: Context, url: String): Boolean {
return runBlocking {
return#runBlocking fetchJsonData(context, url)
}
}
private suspend fun fetchJsonData(context: Context, url: String): Boolean {
return runBlocking {
val client: HttpClient(OkHttp) {
install(JsonFeature) {}
}
val data = async {
client.get<String>(url)
}
try {
val json = data.await()
withContext(Dispatchers.IO) {
context
.openFileOutput("imageFile.json", Context.MODE_PRIVATE)
.use { it.write(json.toByteArray()) }
}
} catch (e: Exception) {
return#runBlocking false
}
}
}
This works and gets the file written locally. Then I have to get the images based on the contents of the file.
suspend fun getImages(context: Context) {
val client = HttpClient(OkHttp)
// Gets the image list from the json file
val imageList = getImageList(context)
for (image in imageList) {
val imageName = image.name
val imageUrl = image.url
runBlocking {
client.downloadFile(context, imageName, imageUrl)
.collect { download ->
if (download == Downloader.Success) {
Log.e("SDK Image Downloader", "Successfully downloaded $imageName.")
} else {
Log.i("SDK Image Downloader", "Failed to download $imageName.")
}
}
}
}
}
private suspend fun HttpClient
.downloadFile(context: Context, fileName: String, url: String): Flow<Downloader> {
return flow {
val response = this#downloadFile.request<HttpResponse> {
url(url)
method = HttpMethod.Get
}
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data, offset, data.size)
offset += currentRead
} while (currentRead > 0)
if (response.status.isSuccess()) {
withContext(Dispatchers.IO) {
val dataPath =
"${context.filesDir.absolutePath}${File.separator}${fileName}"
File(dataPath).writeBytes(data)
}
emit(Downloader.Success)
} else {
emit(Downloader.Error("Error downloading image $fileName"))
}
}
}
If the file is already on the device and I am not attempting to redownload it, this also works. The issue is when I try to get the file and then the images in order when the app is first run. Here is an example of how I am trying to call it:
lateinit var loaded: Deferred<Boolean>
lateinit var imagesLoaded: Deferred<Unit>
#InternalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val context: Context = this
loaded = GlobalScope.async(Dispatchers.Default) {
init(context)
}
GlobalScope.launch { loaded.await() }
imagesLoaded = GlobalScope.async(Dispatchers.Default) {
getDeviceImages(context)
}
GlobalScope.launch { imagesLoaded.await() }
configureImageList(getImageList(context))
}
fun configureImageList(imageList: MutableList<Image>) {
val imageListAdapter = ImageListAdapter(this, imageList)
with(image_list) {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
itemAnimator = null
adapter = imageListAdapter
}
}
This falls apart. So the two scenarios that play out are:
I run this code as-is: The file is downloaded, and ~75% of the images are downloaded before the app crashes with a java.io.IOException: unexpected end of stream on the url. So it seems that the images are starting to download before the file is fully written.
I run the app once without the image code. The file is downloaded. I comment out the file downloading code, and uncomment out the image downloading code. The images are downloaded, the app works as I want. This suggests to me that it would work if the first coroutine was actually finished before the second one started.
I have written and rewritten this code as many ways as I could think of, but I cannot get it to run without incident with both the file writing and image downloading completing successfully.
What am I doing wrong in trying to get these coroutines to complete consecutively?
I just figured it out after stumbling upon this question. It appears as though my HttpClient objects were sharing a connection instead of creating new ones, and when the server closed the connection it caused in-flight operations to unexpectedly end. So the solution is:
val client = HttpClient(OkHttp) {
defaultRequest {
header("Connection", "close")
}
}
I added this to each of the Ktor client calls and it now works as intended.

What's the right way to get permissions for phone call intent

How to request permissions using Kotlin.
I am trying to make a phone call function
fun buChargeEvent(view: View){
var number: Int = txtCharge.text.toString().toInt()
val intentChrage = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$number")
startActivity(intentChrage)
}
I added user permissions in manifest
but still having the same
error .
You need to add permission to your manifest first
<uses-permission android:name="android.permission.CALL_PHONE" />
After permission added in manifest following code would work fine for you "Number_to_call" will be youe number that is need to be replaced
val call = Intent(Intent.ACTION_DIAL)
call.setData(Uri.parse("tel:" +"Number_to_call"))
startActivity(call)
You need to add the run time permission. Download the source code from here
//Click function of layout:
rl_call.setOnClickListener {
if (boolean_call) {
phonecall()
}else {
fn_permission(Manifest.permission.CALL_PHONE,CALLMODE)
}
}
// Request permission response
fun fn_permission(permission:String,mode:Int){
requestPermissions(permission, object : PermissionCallBack {
override fun permissionGranted() {
super.permissionGranted()
Log.v("Call permissions", "Granted")
boolean_call=true
phonecall()
}
override fun permissionDenied() {
super.permissionDenied()
Log.v("Call permissions", "Denied")
boolean_call=false
}
})
}
// function to call intent
fun phonecall() {
val intent = Intent(Intent.ACTION_CALL);
intent.data = Uri.parse("tel:1234567890s")
startActivity(intent)
}
Thanks!
First you need to add permission to your manifest first :
<uses-permission android:name="android.permission.CALL_PHONE" />
This bit of code is used on the place of your method :
fun buChargeEvent(view: View) {
var number: Int = txtCharge.text.toString().toInt()
val callIntent = Intent(Intent.ACTION_CALL)
callIntent.data = Uri.parse("tel:$number")
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this as Activity,
Manifest.permission.CALL_PHONE)) {
} else {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CALL_PHONE),
MY_PERMISSIONS_REQUEST_CALL_PHONE)
}
}
startActivity(callIntent)
}
You need to request the runtime permission, since Android 6.0 certain permissions require you to ask at install and again at runtime.
Following the instructions here explains how to ask for permission at runtime.
This is the complete code for runtime permissions for Call Phone
Step 1:- add permission in manifest
<uses-permission android:name="android.permission.CALL_PHONE" />
Step 2:- Call this method checkAndroidVersion() in onCreate()
fun checkAndroidVersion() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkAndRequestPermissions()) {
} else {
}
} else {
// do code for pre-lollipop devices
}
}
val REQUEST_ID_MULTIPLE_PERMISSIONS = 1
fun checkAndRequestPermissions(): Boolean {
val call = ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CALL_PHONE)
val listPermissionsNeeded = ArrayList<String>()
if (call != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.CALL_PHONE)
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this#MainActivity, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS)
return false
}
return true
}
fun checkAndRequestPermissions(): Boolean {
val call = ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CALL_PHONE)
val listPermissionsNeeded = ArrayList<String>()
if (call != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.CALL_PHONE)
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this#MainActivity, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS)
return false
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
Log.d("in fragment on request", "Permission callback called-------")
when (requestCode) {
REQUEST_ID_MULTIPLE_PERMISSIONS -> {
val perms = HashMap<String, Int>()
// Initialize the map with both permissions
perms[Manifest.permission.CALL_PHONE] = PackageManager.PERMISSION_GRANTED
// Fill with actual results from user
if (grantResults.size > 0) {
for (i in permissions.indices)
perms[permissions[i]] = grantResults[i]
// Check for both permissions
if (perms[Manifest.permission.CALL_PHONE] == PackageManager.PERMISSION_GRANTED
) {
print("Storage permissions are required")
// process the normal flow
//else any one or both the permissions are not granted
} else {
Log.d("in fragment on request", "Some permissions are not granted ask again ")
//permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
// // shouldShowRequestPermissionRationale will return true
//show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
if (ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.READ_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)) {
showDialogOK("Call permission is required for this app",
DialogInterface.OnClickListener { dialog, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> checkAndRequestPermissions()
DialogInterface.BUTTON_NEGATIVE -> {
}
}// proceed with logic by disabling the related features or quit the app.
})
} else {
Toast.makeText(this#MainActivity, "Go to settings and enable permissions", Toast.LENGTH_LONG)
.show()
// //proceed with logic by disabling the related features or quit the app.
}//permission is denied (and never ask again is checked)
//shouldShowRequestPermissionRationale will return false
}
}
}
}
}
fun showDialogOK(message: String, okListener: DialogInterface.OnClickListener) {
AlertDialog.Builder(this#MainActivity)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show()
}
**Step 3**:- On button click
fun buChargeEvent(view: View){
if(checkAndRequestPermissions(){
var number: Int = txtCharge.text.toString().toInt()
val intentChrage = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$number")
startActivity(intentChrage)
}
}