Async task could not keep up with for loop (firebase) - android

for (Uri mUri : mSelected)
{
imagesRef = storageRef.child(postid + mUri.getLastPathSegment());
imagesRef.putFile(mUri).addOnSuccessListener(task4 ->
db.collection("posts").document(postid).update("photo_id", FieldValue.arrayUnion(imagesRef.getDownloadUrl())));
}
So, now i am working with firestore and firebase storage. I uploaded (multiple) images to the storage, and when it is uploaded, i get the download url and wants to add it into my firestore. So, the problem is, only the last image is added into firestore, all images are added into storage, but only the last imgUrl is added into firestore. Since firebase uses async tasks i suspect it is because the update task could not keep up with the loop. Any help is very much appreciated !!!

I think the problem might be with imagesRef, it is declared outside of for (Uri mUri : mSelected) {...} and therefore it is being replaced before addOnSuccessListener(...) responds.
So, declare it locally to for (Uri mUri : mSelected) {...} and see if it will not resolve the issue. Like this
for (Uri mUri : mSelected)
{
var imagesRef = storageRef.child(postid + mUri.getLastPathSegment());
imagesRef.putFile(mUri).addOnSuccessListener(task4 ->
db.collection("posts").document(postid).update("photo_id", FieldValue.arrayUnion(imagesRef.getDownloadUrl())));
}

Related

Unity: How to upload a zip file to firebase storage without FileContentProvider?

I have a minimal working example for uploading a zip file to firebase from my Unity-Android app. The sample works fine in playing the app in the Unity-Editor mode.
However, when building for Android, I'm getting the following error:
System.AggregateException: One or more errors occured. (No content provider: /storage/emulated/0/Android/data/com.MyCompany.MyApp/cache/myFile.zip)
Firesbase.Sotrage.StorageException: No content provider
When googling the issue, I get instructions on how to edit a "Custom AndroidManifest.xml" and adding a FileProvider class using native Android code (by compiling a library project using Android Studio etc.).
This looks like a complete overkill for my problem. Shouldn't there be a nice and easy way to share a file, created by my app, stored in the app's own folder with firebase?
So I found a workaround: Instead of trying to send the file directly to firebase, I can
first read the file into RAM as a byte array
then send the bytearray to firebase
Here some code:
FirebaseStorage storage;
StorageReference storageRef;
storage = FirebaseStorage.DefaultInstance;
storageRef = storage.RootReference;
byte[] fileData = File.ReadAllBytes(localFilePath);
StorageReference storageDataRef = storageRef.Child(firebaseFilename);
storageDataRef.PutBytesAsync(fileData)
.ContinueWith((Task<StorageMetadata> task) => {
if (task.IsFaulted || task.IsCanceled) {
Debug.Log(task.Exception.ToString());
// Uh-oh, an error occurred!
}
else {
// Metadata contains file metadata such as size, content-type, and md5hash.
StorageMetadata metadata = task.Result;
string md5Hash = metadata.Md5Hash;
Debug.Log("Finished uploading...");
Debug.Log("md5 hash = " + md5Hash);
}
});
Now I'm not getting any "FileContentProvider" errors anymore. Not sure why I have to do this though...

How to handle application created images?

So, I would like to upload files to server, and my application can run without network, and because of this, I have to save the files, that the user picks and upload later.
I don't want to save thease pictures into database, bacause as I read it is a bad practice, so I decided to I create a copy of the file, and after that I save the copied file uri string to database.
It is working good, but if I close the application, and opened it again, the uri is not valid, or probably I don't have permission anymore to read or write.
So how can I perform this behavior?
And why lose the application the uri permission, if it is created by himself?
I tired to get persistable uri permission, but I get an java.lang.SecurityException: No persistable permission grants found exception.
File copy
val copyOfFile = createFile(
application.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
fileMetaData.extension,
fileMetaData.fileName,
true
)
val outPut = FileOutputStream(copyOfFile)
inputStream.copyTo(outPut)
val newFileUri =
FileProvider.getUriForFile(application, application.packageName, copyOfFile)
application.contentResolver.takePersistableUriPermission(
newFileUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return CopyFileResult(
copyOfFile.name,
fileMetaData.extension,
newFileUri.toString()
)
Open uri
val uri = Uri.parse(document.uriString!!)
val bitmap = documentManagerService.getBitmapFromUri(uri)
I found my mistake.
I save the filePath to database instead of uri string, and later the file is exists and I can use.

Media scanner for secondary storage on Android Q

With the newer Android Q many things changed, especially with scoped storage and gradual deprecation of file:/// URIs. The problem is the lack of documentation on how to handle media files correctly on Android Q devices.
I have a media file (audio) management application and I could not find yet a reliable way to tell to the OS that I performed a change to a file so that it can update its MediaStore record.
Option #1: MediaScannerService
MediaScannerConnection.scanFile(context, new String[]{ filePath }, new String[]{"audio/*"}, new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(String s, Uri uri) {
}
});
Works with file:// URIs from primary storage
Not works with file:// URIs from secondary storage (such as removable storage)
Not works with any content:// URI
Option #2: broadcast
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
Not working at all
Soon deprecated
Option #3: manual MediaStore insertion
AudioFileContentValues are some column values from MediaStore.Audio.AudioColumns.
Old method based on file:// URI:
Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
MediaStore.Audio.Media.getContentUriForPath is deprecated
Still not working
Newer method based on what I could put together from documentation:
Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);
Where correctVolume would be external from primary storage, while it would be something like 0000-0000 for secondary storage, depending on where the file is located.
Insertion returns a content URI such as content://media/external/audio/media/125 but then no record is persisted inside MediaStore for files located in primary storage
Insertion fails with no URI returned and no record in MediaStore
These are more or less all the methods available in previous Android versions but none of them now allow me to notify the system that I changed some audio file metadata and to get Android to update MediaStore records. Event though option #1 is partially working, this could never be a valuable solution because it's clearly not supporting content URIs.
Is there any reliable way to trigger media scan on Android Q, despite where the file is located? We shouldn't even care about file location, according to Google, since we will soon only use content URIs. MediaStore has always been a little frustrating in my opinion, but now the situation is pretty worse.
I'm also currently struggling with that.
I think what you want to do you cannot do any longer once you are on Android Q, because you are not allowed to access the Music directory on Q. You are only allowed to create and access files in directories you created. You did not create the music directory.
Now every change to the Media has to happen threw the MediaStore. So you insert your Music file beforehand and then get an outputstream from the MediaStore to write to it. All the changes on Q on Media should be done threw the MediaStore hence you informing the MediaStore of changes cannot even occur anymore, because you never directly access the File.
This has one giant caviat in that all the new things in MediaStore that make that possible do not exist in older versions of Android. So I do currently believe that you will need to implement everything twice, sadly. At least if you want to actively influences where your music is saved to that is.
Those two MediaStore columns are new in Q and do not exist before Q, witch you'll probably need to use in Q
MediaStore.Audio.Media.RELATIVE_PATH with that you can influence the path where it's saved. So I put "Music/MyAppName/MyLibraryName" there and that will end up saving "song.mp3" into "Music/MyAppName/MyLibraryName/song.mp3"
MediaStore.Audio.Media.IS_PENDING this you should be setting to 1 while the song is still being written and then afterwards you can update it to 0.
I've also now started to implement things twice with if checks for Android versions. It's annoying. I don't want to do it. But it seems like that's the only way.
I'm just gonna put a bit of code here on how I managed inserting music on Android.Q and below. It's not perfect. I have to specify the MIME type for Q, because flacs would now become .flac.mp3 somehow, because it does not quite seem to get that.
So, anyways this is a part that I have updated already to work with Q and before, it downloads a Music file from a music player on my NAS. The app is written in kotlin, not sure if that's a problem for you.
override fun execute(library : Library, remoteApi: RemoteApi, ctx: Context) : Boolean {
var success = false
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues().apply {
put(MediaStore.Audio.Media.RELATIVE_PATH, library.rootFolderRelativePath)
put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename())
put(MediaStore.Audio.Media.IS_PENDING, 1)
}
val collection = MediaStore.Audio.Media
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uri = ctx.contentResolver.insert(collection, values)
ctx.contentResolver.openOutputStream(uri!!).use {
success = remoteApi.downloadMusic(remoteLibraryEntry, it!!)
}
if(success) {
values.clear()
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
ctx.contentResolver.update(uri, values, null, null)
} else {
ctx.contentResolver.delete(uri, null, null)
}
} else {
val file = File("${library.rootFolderPublicDirectory}/${remoteLibraryEntry.getFilename()}")
if(file.exists()) file.delete()
success = remoteApi.downloadMusic(remoteLibraryEntry, file.outputStream())
if (success) {
MediaScannerConnection.scanFile(ctx, arrayOf(file.path), arrayOf("audio/*")) { _, uri ->
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
}
}
}
return success
}
And the MediaStoreHelper Method being this here
fun getSongId(uri : Uri) : Long {
val cursor = resolver.query(uri, arrayOf(Media._ID), null, null, null)
return if(cursor != null && cursor.moveToNext()) {
val idIndex = cursor.getColumnIndex(Media._ID)
val id = cursor.getLong(idIndex)
cursor.close()
id
} else {
cursor?.close()
-1
}
}
One thing when you do not specify the MIME type it seems to assume mp3 is the MIME type. So .flac files would get saved as name.flac.mp3, because it adds the mp3 file type if there is none and it thinks it's a mp3. It does not add another .mp3 for mp3 files. I don't currently have the MIME type anywhere... so I'm gonna go ahead and do this now, I guess.
There is also a helpful google IO talk about scoped/shared storage https://youtu.be/3EtBw5s9iRY
That probably won't answer all of your questions. It sure enough didn't for me. But it was a helpful start to have a rough idea what they even did change to begin with.
For deleting and updating files its kinda the same on Q if you call delete on a mediastore entry, the file will be deleted. Before, Q you have to manually delete the file also. But if you do that on Q your app will crash. So again you have to check wether or not youre on Q or an older version of android and take appropriate actions.

Firebase storage rules cause user does not have access

I am trying to load a user profile image from firebase storage but I keep running into Cause (1 of 1):
class com.google.firebase.storage.StorageException: User does not have permission to access this object.
Authenticating user with:
mFirebaseAuth.signInWithEmailAndPassword(mEmailView.getText().toString(), mPasswordView.getText().toString())
Here are my firebase rules :
service firebase.storage {
match /b/brainbeats-e9839.appspot.com/o {
match /user/{userId}/{allPaths=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
Loading with glide like this:
StorageReference storageReference = FirebaseStorage.getInstance().getReference();
StorageReference imageStorage = storageReference.child("images/" + user.getArtistProfileImage());
GlideApp.with(this)
.load(imageStorage)
.into(mArtistCoverImage);
Well your rule grants access for the following :
/user/{userId}/{allPaths=**}
so any path under user then a userid location and then anything
But you are accessing
/images/xyzpath
Now since this isn't under /user/ variable userid/ , you get an exception.
why not shift images under user node like :
/user/{userId}/images/yourpath
This should stop giving errors if the auth rule matches. If it's an unauthorized user accessing another userid, it will still crash so best put everything in a try catch block

Why Images.Media.insertImage return null

I have some code where I run the method MediaStore.Images.Media.insertImage (inserting it from a source not a file name), This code saves the image to the MediaStore and returns the uri of the image. I know that when it fails for any reason it will return null instead of a uri. This image has been downloaded multiple times by many people and every once in a while it will return null from this method. I have never had this happen to me so I have no idea what is going on. What are reasons why this could happen? There is another post with the same issue but the answer is a link to the source code for MediaStore but that link goes to a page saying the link is unavailable. Any help would be appreciated. Thanks.
After removing my SD card I got this error so I know that could be a reason, I'm not sure but I feel that it would also happen if the card was full. Still just wondering if there could be another reason too.
It seems to happen when you don't have an /sdcard/DCIM/Camera directory on some versions of Android. Creating this directory (and having the permission) solved the problem for me.
MediaStore.Images.Media.insertImage is actually accessing external storage to save the image.
Some important reminders which might be causing your app to fail:
A USB connection will block SD card usage if in Mass Storage mode.
There may be other factors that might lead to an SD card being inaccessible, so make sure that you can access the SD card using a file browser first.
Make sure that your permissions are correctly configured with android.permission.WRITE_EXTERNAL_STORAGE
Posting this here for completeness, as I was getting a null from insertImage, and the cause was 1.
I faced the same issue and nothing worked. But finally after 3 hours it worked.
Solution: I found that It works fine until we delete that image from our image gallery. After that It starts returning null. But suddenly I tried changing title and description name and it worked like a charm.
So I've added a date with the title and description of bitmap. In case, user deletes bitmap manually from file manager. Still It works.
private fun insertImage(cr: ContentResolver,
source: Bitmap?,
title: String,
description: String
): String? {
val sdf = SimpleDateFormat("MM-dd-yyyy-hh.mm.ss.SSS.a", Locale.US)
val date=sdf.format(Date())
val values = ContentValues()
values.put(Images.Media.TITLE, title)
values.put(Images.Media.DISPLAY_NAME, title+date)
values.put(Images.Media.DESCRIPTION, description+date)
values.put(Images.Media.MIME_TYPE, "image/jpeg")
// Add the date meta data to ensure the image is added at the front of the gallery
values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
values.put(Images.Media.DATE_TAKEN, System.currentTimeMillis())
var url: Uri? = null
var stringUrl: String? = null /* value to be returned */
try {
url = cr.insert(Images.Media.EXTERNAL_CONTENT_URI, values)
if (source != null) {
val imageOut = cr.openOutputStream(url!!)
try {
source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut)
} finally {
imageOut!!.close()
}
} else {
cr.delete(url!!, null, null)
url = null
}
} catch (e: Exception) {
if (url != null) {
cr.delete(url, null, null)
url = null
}
}
if (url != null) {
stringUrl = url.toString()
}
return stringUrl
}
I Implemented to share news bitmap in this app and it's working fine.
Enjoy!
I ran into this issue when I was running tests on an emulator with a fresh sdcard image. I was able to solve the problem by creating /sdcard/Pictures with
new File("/sdcard/Pictures").mkdirs();
Make sure you have added permissions in your manifest file.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Then add this code before calling
MediaStore.Images.Media.insertImage
File sdcard = Environment.getExternalStorageDirectory();
if (sdcard != null) {
File mediaDir = new File(sdcard, "DCIM/Camera");
if (!mediaDir.exists()) {
mediaDir.mkdirs();
}
}
I was executing MediaStore.Images.Media.insertImage before getting user permission to WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE which was causing a null path
So make sure user has allowed for permission and then store image
When i had such a problem i fixed it with adding permissions:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Try this:
MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, "filename", null);
Android 10 needs the actual file name on third paramter of insertImage() method.

Categories

Resources