I have a recyclerview that displays multiple images from Firebase Realtime Database. The recyclerview also has a button within it. I want this button to allow users to be able to download these images ONE AT A TIME once they click it.
Once users click "download" I want the images to be saved to their device storage. I've tried multiple solutions for this, but they weren't helpful as they were for either Firestore Database or only allowed for one image to be downloaded.
Code
class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.download_btn.setOnClickListener {
downloadFile()
}
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
}
override fun getItemCount(): Int {
return abstractList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)
}
private fun downloadFile() {
val storage = FirebaseStorage.getInstance()
val storageRef = storage.getReferenceFromUrl("https://notes-72413.firebaseio.com/")
val islandRef = storageRef.child("Abstract")
val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
if (!rootPath.exists()) {
rootPath.mkdirs()
}
val localFile = File(rootPath, "imageName.jpeg")
islandRef.getFile(localFile)
.addOnSuccessListener(OnSuccessListener<FileDownloadTask.TaskSnapshot?> {
Log.e("firebase ", ";local tem file created created $localFile")
// updateDb(timestamp,localFile.toString(),position);
}).addOnFailureListener(OnFailureListener { exception ->
Log.e(
"firebase ",
";local tem file not created created $exception"
)
})
}
companion object {
private const val Tag = "RecyclerView"
}
I've tried this code, but once I click the "download" button it immediately crashes and Logcat says Firebase Storage URLs must point to an object in your Storage Bucket. Please obtain a URL using the Firebase Console or getDownloadUrl()
My Firebase Realtime Database
There's 64 files in total
Summary
I have a recyclerview that displays images from Firebase Realtime Database. Once users click the "download" button, it only downloads one image at a time to their device storage.
Update
private fun downloadFile() {
val storage = FirebaseStorage.getInstance()
val storageRef = storage.getReferenceFromUrl("abstract")
val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
if (!rootPath.exists()) {
rootPath.mkdirs()
}
val localFile = File(rootPath, "imageName.jpeg")
storageRef.child("Abstract").downloadUrl.addOnSuccessListener { Log.e("firebase ", ";local tem file created created $localFile")
}.addOnFailureListener(OnFailureListener { exception ->
Log.e("firebase ", ";local tem file not created created $exception")
})
}
These are the changes I made to my downloadFile function, but I still get an error:
The storage Uri could not be parsed
Second update
2022-06-11 21:36:00.536 29751-29751/com.khumomashapa.notes E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.khumomashapa.notes, PID: 29751
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:523)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
Caused by: java.net.MalformedURLException: no protocol:
at java.net.URL.<init>(URL.java:601)
at java.net.URL.<init>(URL.java:498)
at java.net.URL.<init>(URL.java:447)
at com.khumomashapa.notes.adapter.AbstractAdapter.downloadFile(AbstractAdapter.kt:57)
at com.khumomashapa.notes.adapter.AbstractAdapter.onBindViewHolder$lambda-0(AbstractAdapter.kt:35)
at com.khumomashapa.notes.adapter.AbstractAdapter.$r8$lambda$Rrmx0DFlwJu1z6QtjG8WCQp6NQQ(Unknown Source:0)
at com.khumomashapa.notes.adapter.AbstractAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7216)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1217)
at android.view.View.performClickInternal(View.java:7190)
at android.view.View.access$3500(View.java:827)
at android.view.View$PerformClick.run(View.java:27663)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8349)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
Code
class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.download_btn.setOnClickListener {
downloadFile(url = String(), file = String.toString())
}
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
}
override fun getItemCount(): Int {
return abstractList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)
}
@Throws(IOException::class)
private fun downloadFile(url: String, file: String) {
val urlObj = URL(url)
val fileObj = File(file)
val conn = urlObj.openConnection()
val buffer = ByteArray(1024)
object : BufferedOutputStream(FileOutputStream(fileObj)) {
var `in` = BufferedInputStream(conn.getInputStream())
init {
var read: Int
while (`in`.read(buffer, 0, buffer.size) >= 0.also { read = it });
run {
out.write(buffer, 0, read)
}
out.flush()
}
}.use { out -> }
}
I found the perfect solution to my problem. All I had to do was create an OnItemClick interface to get a different result for each item click and use Download manager to download the images.
override fun onItemClick(item: String, pos:Int) {
abstractData = item
positionItem = pos
if (checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ){
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQ_CODE)
}else{
startDownloading()
}
Toast.makeText(requireActivity(), "Saved to Internal storage/Pictures/AbstractWallpaper", Toast.LENGTH_LONG).show()
}
private fun startDownloading() {
val request = DownloadManager.Request(Uri.parse(abstractData))
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setTitle("Abstract Wallpaper")
request.setDescription("Your image is downloading")
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "AbstractWallpapers")
val manager = activity?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
manager.enqueue(request)
Toast.makeText(requireActivity(), "Download is starting...", Toast.LENGTH_LONG).show()
}