I'm newish to Kotlin, and new to StackOverflow. This is my first question.
I'm trying to retrieve and parse metadata stored in Google Firebase storage, using Kotlin. I have successfully retrieved the file and displayed it, and am able to get a reference to the metadata using val valName = referenceName.metadata. At this point I would like to retrieve the custom metadata that is stored in that val and parse it to a string. Printing the contents of the metadata using toString() returns com.google.android.gms.tasks.taskId to the console.
I've visited the docs and used them for a lot of my project so far, they are located at:
https://firebase.google.com/docs/storage/android/file-metadata, but am stuck on what to do next.
Thanks for your help!
My code:
// create an instance of the firebase storage
val storage = FirebaseStorage.getInstance()
// create a reference to storage
val storageRef = storage.reference
// create a reference to the featured content image
val filmRef = storageRef.child("featured/film.jpg")
// place the image metadata in a val - this appears to be working
val filmMeta = filmRef.metadata
// parse metadata to a string
// ****** what to do next? ********
val filmId = filmMeta.customMetadata("id") // <--- this does not work
You should add your customMetedata in storage ref
val metadata = storageMetadata {
setCustomMetadata("id", "filmId")
}
filmRef.updateMetadata(metadata).addOnSuccessListener {
// Updated metadata is in storageMetadata
val filmId = it.getCustomMetadata("id")
}.addOnFailureListener {
}
Then you can use like this:
filmRef.metadata.addOnSuccessListener {
val filmId = it.getCustomMetadata("id")
}.addOnFailureListener {
}
Related
I alredy searched here in the forum but i didn't find nothing like that. I want to get an Object from Firebase Firestore, but I can't manipulate the object that I am receiving. How can I separate the imageLink from the imageTitle?
The database structure is:
The code I am using is:
firebaseFirestore.collection("image").get()
.addOnSuccessListener { documentSnapshot ->
val imageData = documentSnapshot.documents[0].data?.getValue("imageData")
}
But, when I do that, I receive something like that:
How can I get the imageLink from the imageTitle separately?
You can try casting imageData as a Map as shown below:
db.collection("images").limit(1).get().addOnSuccessListener { querySnapshot ->
val imageData =
querySnapshot.documents[0].data!!.getValue("imageData")?.let { it as Map<*, *> }
val imageLink = imageData?.get("imageLink")
val imageTitle = imageData?.get("imageTitle")
Log.d("MainActivity", "imageLink: $imageLink")
Log.d("MainActivity", "imageTitle: $imageTitle")
}
Checkout the documentation for let for more information.
You are calling get() on a CollectionReference that will fetch all documents from the collection (if you add any). If you only want to fetch 1st document from the collection, you can add limit(1) that'll save any additional charges.
I'm using firebase in my project and i'm testing all the functionalities which includes firebase components and i recently tried to test firebase database but it is throwing an error which i could not understand if someone could help , i would appreciate it , Thank you
error that i'm getting
Attempt to invoke virtual method 'com.google.firebase.database.DatabaseReference com.google.firebase.database.DatabaseReference.child(java.lang.String)' on a null object reference
at com.revert.journey.app.chatui.ChatHomeActivityTest.testCaseSendMessage
This is my testing code
#Test
fun testCaseSendMessage(){
val databaseMock = mock(DatabaseReference::class.java)
ServiceLocator.reference = databaseMock
`when`(databaseMock.child("Messages").child("1251515").setValue(Utils.message()))
.thenReturn(isNotNull())
}
This is my real code
val messageMap = hashMapOf<String,Any>()
messageMap["userName"] = userName
messageMap["userMessage"] = message
messageMap["userPic"] = userPic
messageMap["messageTiming"] = Calendar.getInstance().timeInMillis.toString()
messageMap["chatImage"] = downloadUrl
messageMap["uid"] = firebaseAuth.currentUser!!.uid
ServiceLocator.reference
.child("Messages")
.child(System.currentTimeMillis().toString())
.setValue(messageMap).await()
ServiceLocator class
object ServiceLocator {
var firebaseAuth = FirebaseAuth.getInstance()
var reference = FirebaseDatabase.getInstance().reference
}
Image Sample from my firebase
UPDATE
var base = ServiceLocator.reference.child("Messages")
var child = base.child(System.currentTimeMillis().toString())
child.setValue(messageMap).await()
If you want to Mock the full call chain you could create mocks for the intermediate states like this (I don't know all the right types to use here for realtime database, but a similar approach works for Firestore)
val databaseMock = mock(DatabaseReference::class.java)
val childMock = mock(Reference::class.java)
val mockTask = mock(??) // set type to whatever "setValue" returns
doReturn(childMock).`when`(databaseMock).child(anyString())
doReturn(childMock).`when`(childMock).child(anyString())
doReturn(mockTask).`when`(childMock).setValue(any())
If you want to actually test that the correct value was set, you can add a listener to the mock to intercept the actual value passed to it
doAnswer { invocation ->
val args = invocation.arguments
val l = args[0] as Map<String,Any>
//add tests here to assert that the map values you sent are correct
null
}.`when`(childMock).setValue(any())
Debugging Tips
If you want to diagnose what is going on in a scenario like this you can change the chained call in your real code to something like the code below. Then if one of the calls returns null you will know exactly which one it is and can add the missing mock for it.
val db = ServiceLocator.reference
val cm = db.child("Messages")
val ct = cm.child(System.currentTimeMillis().toString())
val response = ct.setValue(messageMap)
response.await()
None of these call should access your database in the test, so the actual database schema does not matter. All that matters is getting the mocks set correctly (since you are using a mock database anyway)
User should be able to upload multiple images in one post.
I tried to add all urls to a ArrayList and then to store them to firestore.
All I get are errors.
"imageList" returns always null
Here is my code:
fun uploadPost(images:ArrayList<Uri>, imageCount: Int, description:String){
images.forEach { image->
val imageRef = storageReferenence.child(System.currentTimeMillis().toString()
+"."+ image.lastPathSegment)
val uploadTask = imageRef.putFile((image))
uploadTask.addOnSuccessListener {
val downloadUrl = imageRef.downloadUrl
downloadUrl.addOnSuccessListener {uri->
Log.d("IMAGES", uri.toString())
imageList!!.add(uri.toString())
count++
}
}
}
//firebaseRepository.firestoreDB.collection("post").document(firebaseRepository.userid.toString()).update(post)
}
You can see that the Log has the url but when I try to add it to the imageList
it fails to do so.
THIS CAUSES THE ERROR: imageList!!.add(uri.toString())
ERROR MSG: AddViewModel$uploadPost$$inlined$forEach$lambda$1$1.onSuccess
I don`t really know what is better to store the images as an array or to store each image like this: image1:url... , image2: url..
I need them to be part of the same document.
At the moment as a sample I'm recreating instagram-like post, where user can post a description and image (along with username since I'm still not configuring authentication). So far as follows I can only post username and description. Along side of that I can load new image as a source into the imageview on the screen.
My data class is:
data class Post(val username: String,
val timestamp: Date,
val postTxt: String,
val numLikes: Int,
val numComments: Int,
val documentId: String)
I put data in a hashmap as follows:
val data = hashMapOf(
NUM_LIKES to 0,
NUM_COMMENTS to 0,
POST_TXT to addPostTxt.text.toString().trim(),
TIMESTAMP to FieldValue.serverTimestamp(),
USERNAME to addUsernameTxt.text.toString().trim()
)
and then pass it to upload to firestore:
FirebaseFirestore.getInstance().collection(POST_REF)
.add(data)
.addOnSuccessListener { finish() }
.addOnFailureListener {
Log.e("Exception", "Could not add post: $it")
toast("Could not add post")
}
And to get the data I created a function which takes snapshot of type QuerySnapshot like so:
// Clear all the posts before relaod
posts.clear()
snapshot.documents.forEach { document ->
val data = document.data
val name = data?.get(USERNAME) as String
val timestamp = data[TIMESTAMP] as Date
val postTxt = data[POST_TXT] as String
val numLikes = data[NUM_LIKES] as Long
val numComments = data[NUM_COMMENTS] as Long
val documentId = document.id
val newPost = Post(name, timestamp, postTxt, numLikes.toInt(), numComments.toInt(), documentId)
posts.add(newPost)
}
postsAdapter.notifyDataSetChanged()
So far uploading text, current time of upload works fine for both upload and reading it from the database; however my question here is:
- What is the best way (following current codebase) to upload image to firestore and storage and display it accordingly in the main window (preferably using Glide or picasso)?
It's generally not a good idea to store binary data like images in Firestore. You could very easily exceed the limit for how much data you can store in a document, which is 1MB. Large documents also take more time to load on the client, which is even worse if you don't even need to use the image at the time of the read.
Instead, consider uploading the image to Cloud Storage and saving a path to that location in a field in your document. Then, when it's time to display the image, load it from there.
I am using room persistence library for my android application, Now I have to insert image in my db. I successfully define #Entity for the primitive data type. and also through converter class, i stored all object, date, time. Now I have to store Image. I am not able to understand how we define Column info and entity and how we insert that data as well as read data from the table.
What is the maximum size of data which inserted into the single row? What is max and min size of data in one field in Android SQLite?
It is usually not recommended to store image data into the database.
But however if it is required for your project then you can do so.
Image data are usually stored into db using BLOB data type, Room also provide support for BLOB data type Documentation
You can declare your entity class as mentioned below to store Image data.
#Entity(tableName = "test")
public class Test{
#PrimaryKey
#ColumnInfo(name = "_id")
private int id;
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
private byte[] image;
}
As Pinakin mentioned, it is not recommended to store an image into database and file path would be better but if it is required to store image I would suggest compress the image to below 2 MB (here is an example) to avoid breaking app. Room supports BLOB for image.
Entity class in kotlin:
ImageTest.kt
#Entity
class ImageTest {
#PrimaryKey(autoGenerate = true)
var id: Int = 1
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
var data: ByteArray? = null
}
ImageDao.kt
#Dao
interface ImageTestDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun upsertByReplacement(image: List<ImageTest>)
#Query("SELECT * FROM image")
fun getAll(): List<ImageTest>
#Query("SELECT * FROM image WHERE id IN (:arg0)")
fun findByIds(imageTestIds: List<Int>): List<ImageTest>
#Delete
fun delete(imageTest: ImageTest)
}
Databse.kt
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
#Database(entities = arrayOf(ImageTest::class), version = 1)
#TypeConverters(DataConverters::class)
abstract class Database : RoomDatabase() {
abstract fun getImageTestDao(): ImageTestDao
}
In DatabaseHelper something like
class DatabaseHelper(context: Context) {
init {
DatabaseHelper.context = WeakReference(context)
}
companion object {
private var context: WeakReference<Context>? = null
private const val DATABASE_NAME: String = "image_test_db"
private var singleton: Database? = null
private fun createDatabase(): Database {
return Room.databaseBuilder(context?.get() ?:
throw IllegalStateException("initialize by calling
constructor before calling DatabaseHelper.instance"),
Database::class.java,
DATABASE_NAME)
.build()
}
val instance: Database
#Synchronized get() {
if (null == singleton)
singleton = createDatabase()
return singleton as Database
}
fun setImage(img: Bitmap){
val dao = DatabaseHelper.instance.getImageTestDao()
val imageTest = ImageTest()
imageTest.data = getBytesFromImageMethod(image)//TODO
dao.updsertByReplacement(imageTest)
fun getImage():Bitmap?{
val dao = DatabaseHelper.instance.getImageTestDao()
val imageByteArray = dao.getAll()
return loadImageFromBytes(imageByteArray[0].data)
//change accordingly
}
Correct me if I am wrong. Hope this helps someone out there
Save the image as a file and save the file path Uri to Room
As seen in CameraX's image capture use case, when a photo is successfully taken, the File path reference Uri, savedUri, can be retrieved safely.
Then, the Uri can be converted to a string with savedUri.toString(), and saved to Room.
It's important to ensure the Room file reference is also updated if the file is moved or deleted.
The image String saved in Room may need to be converted back into a Uri to be displayed with an image library such as Glide with Uri.parse(someString).
In the CameraX sample, an image path's Uri can safely be obtained in onImageSaved.
It would then be saved into Room off of the main thread using Kotlin Coroutines or RxJava, preferably in a ViewModel or somewhere that handles the business logic separate from the view logic.
Getting Started with CameraX > 5. Implement ImageCapture use case
private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return
// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
This strategy is outlined in Saving image in Room database on Reddit.
Cloud Storage
Creating a file for the image and saving the file path in Room covers local storage. In order to ensure the images are saved across multiple devices or when if data cache and data are cleared, a form of Cloud Storage is needed to upload the files to and to download and sync with the local storage.