I am currently working on a matching card game where I need to store the images on Firebase. I am uploading the images by a button click when I start the game(doing it automatically creates same problem but the button one is safer) I think the Image isn't getting downloaded fast enough to show on the card face or it might not be working in a sequence with the whole app so the bitmap array gets zero elements inside. My current code is:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min 1) min
val imageID2 = Random().nextInt(max - min 1) min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
/// DUPLICATE
bitmapArray.addAll(bitmapArray)
///SHUFFLE
bitmapArray.shuffle()
Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}
Is there any other way to retrieve image from the Firebase Storage besides downloading and adding it to a temporary file and then decoding it to a bitmap?
I tried anything that I could find. I even tried adding the access tokens of the images to a realtime database and then getting them from there but I failed terribly. Thanks in advance for helping!
CodePudding user response:
Since getFile()
an asynchronous task I would imagine your log statement Log.w("myapplication", bitmapArray.size.toString())
is executing while the bitmapArray is still empty? This would happen because the aDatabase.getFile().addOnSuccessListener {}
and sDatabase.getFile().addOnSuccessListener {}
won't execute until the download finishes, but allow the rest of your function to continue to execute.
What you need to do is await the results of the downloads before continuing with the duplicate and shuffle portions.
getFile()
returns a FileDownloadTask
, which inherits from StorageTask
. StorageTask
has an isComplete()
method -- and a few others the may be useful for errors cases. One option would be to capture the FileDownloadTask
in a variable and not continue executing until your downloads are finished. However, be warned this might freeze up your main thread.
Edit: Instead of checking status on the main thread, you might want to try something like disabling the buttons until the images are ready. See edit comments:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private val numImages = 2 // EDIT total number of images we need to download
private val numImagesReady = AtomicInteger(0) // EDIT count of how many images are currently ready
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min 1) min
val imageID2 = Random().nextInt(max - min 1) min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
// EDIT disable buttons until all images are ready
buttons.forEach {
it.setEnabled(false)
}
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
// SUGGESTION especially if this will be implemented 8x8, you might want to try implementing this in a loop instead of duplicating code
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
// EDIT moved /// DUPLICATE
// EDIT refactor bitmapArray.addAll(bitmapArray)
// EDIT moved ///SHUFFLE
// EDIT moved bitmapArray.shuffle()
// EDIT remove Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}