I get the following error when trying to iterate over the uploadTasks inside an addOnSuccessListener method.
java.lang.ClassCastException: com.google.firebase.storage.UploadTask$TaskSnapshot cannot be cast to com.google.firebase.storage.UploadTask
So how can i get the Download String of each Img inside addOnSuccessListener?
val baos = ByteArrayOutputStream()
val tasks = mutableListOf<UploadTask>()
listImg.forEach {
if(bitmap!!.byteCount != it.byteCount) {
val bitmap = it
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
var uploadTask = spaceRef.putBytes(data)
tasks.add(uploadTask)
}
}
Tasks.whenAllSuccess<UploadTask>(tasks).addOnSuccessListener { uploadTasks ->
//uploadTasks has size of 2
val urls = mutableListOf<Uri>()
lifecycleScope.launch
{
//Error throws here
uploadTasks.forEach{
urls.add(it.await().storage.downloadUrl.await())
}
}
}
The type of whenAllSuccess is <TResult>, so you should use the result type of UploadTask (UploadTask.TaskSnapshot) instead:
Tasks.whenAllSuccess<UploadTask.TaskSnapshot>(tasks).addOnSuccessListener { uploadTasks ->
And then you can drop the the first await() on that last line:
urls.add(it.storage.downloadUrl.await())
Bonus: Don't block the main thread
Note that Tasks.whenAllSuccess() will block the main thread until all uploads succeed, meaning your UI might freeze while uploading files.
To avoid that, consider uploading your files with Coroutines:
val baos = ByteArrayOutputStream()
val urls = mutableListOf<Uri>()
lifecycleScope.launch {
listImg.forEach {
if(bitmap!!.byteCount != it.byteCount) {
val bitmap = it
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
// Upload the image first
val taskSnapshot = spaceRef.putBytes(data).await()
// Get the download Url
val downloadUri = taskSnapshot.storage.downloadUrl.await()
// Add it to the list of Uris
urls.add(downloadUri)
}
}
}
Related
I want to download an image in svg that is in the FirebaseStore, using data method, but the bitmap conversion returns null.
I need returns the svg image and put on the screen
private lateinit var auth: FirebaseAuth
private lateinit var storage: FirebaseStorage
private lateinit var storageRef: StorageReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
auth = Firebase.auth
storage = Firebase.storage
storageRef = storage.reference
val fileRef = storageRef.child("CFM/cfm2.back.svg")
downloadImgData(fileRef)
}
private fun downloadImgData(fileRef: StorageReference?){
if (fileRef != null) {
val ONE_MEGABYTE = (1024 * 1024).toLong()
fileRef.getBytes(ONE_MEGABYTE)
.addOnSuccessListener { bytes ->
val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
imageView.setImageBitmap(bmp)
textview.text = fileRef.name
}
.addOnFailureListener { exception ->
Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show()
}
}
You can use Pixplicity's Sharp for loading SVG images into ImageView.
It provides method to load svg image from InputStream into ImageView.
ByteArray obtained from Firebase Storage can be converted to InputStream by calling bytes.inputStream().
Add the library dependency
dependencies {
implementation 'com.pixplicity.sharp:library:[VERSION_HERE]#aar'
}
This loads the SVG image to imageView
val stream = bytes.inputStream()
Sharp.loadInputStream(stream).into(imageView)
stream.close()
Finally the function looks like,
private fun downloadImgData(fileRef: StorageReference?) {
if (fileRef != null) {
val ONE_MEGABYTE = (1024 * 1024).toLong()
fileRef.getBytes(ONE_MEGABYTE)
.addOnSuccessListener { bytes ->
val stream = bytes.inputStream()
Sharp.loadInputStream(stream).into(imageView)
stream.close()
textview.text = fileRef.name
}
.addOnFailureListener { exception ->
Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show()
}
}
}
Basically, I am trying to download three different images(bitmaps) from a URL and save them to Apps Internal storage, and then use the URI's from the saved file to save a new Entity to my database. I am having a lot of issues with running this in parallel and getting it to work properly. As ideally all three images would be downloaded, saved and URI's returned simultaneously. Most of my issues come from blocking calls that I cannot seem to avoid.
Here's all of the relevant code
private val okHttpClient: OkHttpClient = OkHttpClient()
suspend fun saveImageToDB(networkImageModel: CBImageNetworkModel): Result<Long> {
return withContext(Dispatchers.IO) {
try {
//Upload all three images to local storage
val edgesUri = this.async {
val req = Request.Builder().url(networkImageModel.edgesImageUrl).build()
val response = okHttpClient.newCall(req).execute() // BLOCKING
val btEdges = BitmapFactory.decodeStream(response.body?.byteStream())
return#async saveBitmapToAppStorage(btEdges, ImageType.EDGES)
}
val finalUri = this.async {
val urlFinal = URL(networkImageModel.finalImageUrl) // BLOCKING
val btFinal = BitmapFactory.decodeStream(urlFinal.openStream())
return#async saveBitmapToAppStorage(btFinal, ImageType.FINAL)
}
val labelUri = this.async {
val urlLabels = URL(networkImageModel.labelsImageUrl)
val btLabel = BitmapFactory.decodeStream(urlLabels.openStream())
return#async saveBitmapToAppStorage(btLabel, ImageType.LABELS)
}
awaitAll(edgesUri, finalUri, labelUri)
if(edgesUri.getCompleted() == null || finalUri.getCompleted() == null || labelUri.getCompleted() == null) {
return#withContext Result.failure(Exception("An image couldn't be saved"))
}
} catch (e: Exception) {
Result.failure<Long>(e)
}
try {
// Result.success( db.imageDao().insertImage(image))
Result.success(123) // A placeholder untill I actually get the URI's to create my Db Entity
} catch (e: Exception) {
Timber.e(e)
Result.failure(e)
}
}
}
//Save the bitmap and return Uri or null if failed
private fun saveBitmapToAppStorage(bitmap: Bitmap, imageType: ImageType): Uri? {
val type = when (imageType) {
ImageType.EDGES -> "edges"
ImageType.LABELS -> "labels"
ImageType.FINAL -> "final"
}
val filename = "img_" + System.currentTimeMillis().toString() + "_" + type
val file = File(context.filesDir, filename)
try {
val fos = file.outputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (e: Exception) {
Timber.e(e)
return null
}
return file.toUri()
}
Here I am calling this function
viewModelScope.launch {
val imageID = appRepository.saveImageToDB(imageNetworkModel)
withContext(Dispatchers.Main) {
val uri = Uri.parse("$PAINT_DEEPLINK/$imageID")
navManager.navigate(uri)
}
}
Another issue I am facing is returning the URI in the first place and handling errors. As if one of these parts fails, I'd like to cancel the whole thing and return Result.failure(), but I am unsure on how to achieve that. As returning null just seems meh, I'd much prefer to have an error message or something along those lines.
The doWork() method returns the Result before completing the task, resulting in blocking the main thread.
Some of the answers to this question are: To make the thread wait until the task is completed.
I feel there could be a better way to handle it.
class CloudWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
val fireStore = FirebaseFirestore.getInstance()
#SuppressLint("LogNotTimber")
override fun doWork(): Result {
Log.i("thred_dowork"," :"+Thread.currentThread().name)
val appContext = applicationContext
val dataString = inputData.getString(KEY_NOTE_DATA)
val data = Gson().fromJson(dataString, NoteData::class.java)
makeStatusNotification("Uploading Notes", appContext)
Log.i("uri:"," ${data.uri}")
val resolver = appContext.contentResolver
appContext.grantUriPermission(appContext.packageName,
Uri.parse(data.uri),
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val f = File(data.uri, "profile.jpg")
val picture = BitmapFactory.decodeStream(FileInputStream(f))
// Create a storage reference from our app
val storage = Firebase.storage
// Create a storage reference from our app
val storageRef = storage.reference
val baos = ByteArrayOutputStream()
picture.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val imageDataX: ByteArray = baos.toByteArray()
var pathX = "images/"+System.currentTimeMillis().toString()+".jpg"
val mountainImagesRef = storageRef.child(pathX)
val uploadTask = mountainImagesRef.putBytes(imageDataX)
uploadTask.addOnSuccessListener {
Log.i("sync_","Image uploaded"+ it.metadata!!.path+" : "+it.metadata!!.name)
data.uri= it.metadata!!.path
fireStore.collection("notes")
.add(data)
.addOnSuccessListener {
Log.i("sync_","Sync Succes")
if(isStopped)
Log.i("sync_","Worker Stopped")
else
saveUrl()
}.addOnFailureListener {
Log.i("sync_","Sync Succes")
}
}.addOnFailureListener{
Log.e("sync_","Image not uploaded"+it.toString())
}
Timber.i("sync_data: $dataString")
Log.i("sync_","Called before firestore"+dataString)
return Result.success()
}
private fun saveUrl() {
Log.i("sync_","sleeping for 10 sec. thread_ "+Thread.currentThread().name)
sleep(15000)
if(isStopped)
Log.i("sync_","Worker Stopped")
else
Log.i("sync_","Worker not stopped")
Log.i("sync_","Wake up after 10 sec")
fireStore.collection("employee")
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result!!) {
Log.d("sync_employ", document.id + " => " + document.data)
}
} else {
Log.w("sync_", "Error getting documents.", task.exception)
}
}
}
}
Here I am doing firebase operation.
uploading image to firebase file storage
After successful upload, uploading content to the database.
On successful upload, retrieving some data from the database.
Your suggestions are most welcome here.
I'm using Storage Access Network to pick file and save in internal storage so that app can use if in future.
I'm getting URI without any issues. It's something like content://com.android.providers.media.documents/document/image%3A141274
Problem comes when I'm trying to save image into internal directory. Code passes without crashes, image with same size is saved into internal directory (I can see it in device Explorer: https://take.ms/3TwBS).
But image itself is broken and can't be opened.
Here's code I'm using (after getting URI)
val destinationFile = File("${context.filesDir.absolutePath}/$fileName")
try {
val writer = FileWriter(destinationFile)
writer.append(readTextFromUri(it))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
#Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
val inputStream = activity!!.contentResolver.openInputStream(uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String? = null
while ({ line = reader.readLine(); line }() != null) {
stringBuilder.append(line)
}
inputStream?.close()
reader.close()
return stringBuilder.toString()
}
As #CommonsWare described I should have used proper dealing with files, not texts.
Proper way to do:
private fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
val output = FileOutputStream(File("${filesDir.absoluteFile}/magic.png"))
inputStream?.copyTo(output, 4 * 1024)
}
Or longer way (without extension functions)
fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
inputStream.use {
val directory = getDir("test", Context.MODE_PRIVATE)
val file = File(directory, "correct.txt")
val output = FileOutputStream(file)
output.use {
val buffer = ByteArray(4 * 1024) // or other buffer size
var read: Int = inputStream?.read(buffer) ?: -1
while (read != -1) {
output.write(buffer, 0, read)
read = inputStream?.read(buffer) ?: -1
}
output.flush()
}
}
}
I am new to Android Studio and especially to Kotlin. I need to load image from internet and then save it to phone. I tried to load image with Glide as Bitmap and then save it. But it doesn't work. This code is best thing i found but it doesn't work.
try {
var bitmap = Glide.with(this)
.asBitmap()
.load("https://s3.amazonaws.com/appsdeveloperblog/Micky.jpg")
.apply(RequestOptions().override(100).downsample(DownsampleStrategy.CENTER_INSIDE).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE))
.submit().get()
val wrapper = ContextWrapper(applicationContext)
var file = wrapper.getDir("Images", Context.MODE_PRIVATE)
file = File(file, "img.jpg")
val out = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out)
out.flush()
out.close()
}
catch (e: Exception) {
println(e)
}
How I understood problem is in this ".submit().get()" part of Glide. But if I take it away then compress doesn't work.
submit(): Returns a future that can be used to do a blocking get on a background thread.
get(): Waits if necessary for the computation to complete, and then retrieves its result.
In your case to download an image from a url and save it to internal storage, you should use a background thread to do that. If you calling on main thread, your app might be throws ANR dialog.
Here I will demo how to download and save the image by using AsyncTask API
First write a class to download and save the image.
class DownloadAndSaveImageTask(context: Context) : AsyncTask<String, Unit, Unit>() {
private var mContext: WeakReference<Context> = WeakReference(context)
override fun doInBackground(vararg params: String?) {
val url = params[0]
val requestOptions = RequestOptions().override(100)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
mContext.get()?.let {
val bitmap = Glide.with(it)
.asBitmap()
.load(url)
.apply(requestOptions)
.submit()
.get()
try {
var file = it.getDir("Images", Context.MODE_PRIVATE)
file = File(file, "img.jpg")
val out = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out)
out.flush()
out.close()
Log.i("Seiggailion", "Image saved.")
} catch (e: Exception) {
Log.i("Seiggailion", "Failed to save image.")
}
}
}
}
Then in activity just call
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DownloadAndSaveImageTask(this).execute("https://s3.amazonaws.com/appsdeveloperblog/Micky.jpg")
}
}
If you want to save to internal storage
var file = File(it.filesDir, "Images")
if (!file.exists()) {
file.mkdir()
}
file = File(file, "img.jpg")
It will save the image in path data/data/yourapppackagename/files/Images/img.jpg
Just use this method
fun DownloadImageFromPath(path: String?) {
var `in`: InputStream? = null
var bmp: Bitmap? = null
val iv = findViewById<View>(R.id.imagFullImage) as ImageView
var responseCode = -1
try {
val url = URL(path) //"http://192.xx.xx.xx/mypath/img1.jpg
val con: HttpURLConnection = url.openConnection() as HttpURLConnection
con.setDoInput(true)
con.connect()
responseCode = con.getResponseCode()
if (responseCode == HttpURLConnection.HTTP_OK) {
//download
`in` = con.getInputStream()
bmp = BitmapFactory.decodeStream(`in`)
`in`.close()
iv.setImageBitmap(bmp)
}
} catch (ex: Exception) {
Log.e("Exception", ex.toString())
}
}