To retrieve an image in the gallery, most tutorials show the usage of
startActivityForResult()
It is currently deprecated, I have found this Basics of Intents as a substitute. The code block does return a URI, however, when I use that uri to set an image or to upload to firebase storage, it does not work.
Flow of getting and uploading the image:
Click the profile picture
Pick from file storage or gallery
After choosing, it would automatically set the image in the view and upload it to firebase storage associated with the auth ID of the current logged in user.
I have this in a fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
CoroutineScope(Dispatchers.IO).launch {
sharedViewModel.uploadImage(uri)
}
binding.ivProfilePic.setImageURI(uri)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.ivProfilePic.setOnClickListener{
getContent.launch("image/*")
}
}
while this is the code for uploading to firebase:
private val storageRef = Firebase.storage.reference
fun uploadImage(path: Uri?){
val file = Uri.fromFile(File(path?.path!!))
storageRef.child("images/${UUID.randomUUID()}").putFile(file)
}
I think I am missing something here.
I appreciate the help.
I've found the solution!
for the registerActivity, I've used the photo picker support library link
I also added this dependency implementation 'androidx.activity:activity-ktx:1.6.1' as discussed in the documentation.
for the uploading to firebase I think I was just confused with the usage the File class and Uris. I could've passed the Uri at first, I was probably confused with the new keywords (File and Uri) and just jumped and the copy pasted the code.
this is the code I arrived at:
I've moved the registerForActivityResult code outside the onCreate() and just a child of the Fragment class.
private val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
binding.ivProfilePic.setImageURI(uri)
sharedViewModel.uploadImage(uri)
} else {
// insert code for toast showing no media selected
}
}
on onViewCreated():
binding.ivProfilePic.setOnClickListener{
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
and for uploading to firebase:
I've just passed the Uri
fun uploadImage(path: Uri){
storageRef.child("images/${UUID.randomUUID()}").putFile(path)
.addOnSuccessListener {
// Display upload complete
}
}
thank you for the help
Related
Here is some kotlin code I have that is used to implement a download feature in an Android app (using DownloadManager):
(It is not the whole code, but the relevant part for my question)
var brdCstRcvr = object: BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
val id = p1?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (id == downloadID) {
val mgr = applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
val uri:Uri? = mgr.getUriForDownloadedFile(downloadID)
if (uri == null) {
println("We have a problem with this URL: ${urlStr.text.toString()}")
return
}
val inputStream: InputStream = getContentResolver().openInputStream(uri!!)!!
.... more useful code not relevant to the question .....
}
}
.........
}
Though it is mostly working I have a problem with one case. This is the shape of the URL causing trouble:
http://192.168.77.123/FOLDER/Sample.mp3
When running the app I get this message:
We have a problem with this URL: http://192.168.77.123/FOLDER/Sample.mp3
(showing that uri is null)
The other URLs I have used with no issue are all of type https://example.site.net/Sample.mp3
The URL with problems is http (instead of https) and it is on my local network.
But I am not sure this is the cause of the issue.
Any idea on how to catch the cause of this would be helpful.
I know there are many similar topics like this, but they are either outdated or do not work with pdfs.
My question is, how do I download a pdf from cloud firestore and save it into the internal storage? My normal method was to use downloadmanager and save the pdf into external storage, but since this does not work anymore because of scoped storage, I need to find a new way.
Currently, I only know how to create a temporary file and download the pdf from firestore into this temporary file, but not how to save it.
Old Method (with Downloadmanager)
class PDFDownloader(private val context: Context) {
private val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
private val downloadDirectory = Environment.DIRECTORY_DOCUMENTS
private val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
private val pdfIntent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
// DOWNLOADING THE PDF INTO EXTERNAL STORAGE
suspend fun downloadPDF(pdfUri: String, fileName: String) {
val pdfUrl = Firebase.storage.getReferenceFromUrl(pdfUri).downloadUrl.await()
// NOT WORKING ANYMORE BECAUSE OF SCOPED STORAGE
val request = DownloadManager.Request(pdfUrl)
.setTitle(fileName)
.setDestinationInExternalFilesDir(context, downloadDirectory, "$fileName.pdf")
downloadManager.enqueue(request)
}
// RETRIEVING THE PDF FROM EXTERNAL STORAGE
suspend fun getPDFFileAndOpen(fileName: String) {
val regex = "$fileName.pdf"
withContext(Dispatchers.IO) {
val fileList = context.getExternalFilesDir(downloadDirectory)?.listFiles()
val file = fileList?.find { it.name == regex }
pdfIntent.setDataAndType(FileProvider.getUriForFile(context, authority, file), "application/pdf")
context.startActivity(intent)
}
}
}
New Method (without Downloadmanager)
class PDFDownloaderWithoutManager(private val context: Context) {
override suspend fun downloadPDF(uri: StorageReference, fileName: String) {
withContext(Dispatchers.IO) {
// Step 1: Creating a temporary file
val tempFile = File(context.filesDir, "$fileName.pdf")
// Step 2: Downloading the pdf from cloud-firestore into tempFile
uri.getFile(tempFile).await()
// Step 3: Saving the file into internal storage
// OR SAVING INTO EXTERNAL STORAGE WITH SCOPED STORAGE
// (I take whats easier)
?????????????????????????????????????????????????????
}
}
}
// RETRIEVING THE PDF FROM INTERNAL STORAGE
suspend fun getPDFFileAndOpen(fileName: String) {
val regex = "$fileName.pdf"
????????????????????????????????????????????????????????????
}
There is also a method uri.getFile("URI TO SAVE FILE TO), but I don't know how to use that either.
Edit
Using download manager works as intended, and it successfully downloads the file from firestore (yay). The only problem I have is, that I can't open the pdf anymore at android 11. I get the following error:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=content://com.example.app.fileprovider/external_files/Documents/Kalibrierung und Überprüfung.pdf typ=application/pdf flg=0x10000001 }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2067)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727)
at android.app.ContextImpl.startActivity(ContextImpl.java:1023)
at android.app.ContextImpl.startActivity(ContextImpl.java:994)
at android.content.ContextWrapper.startActivity(ContextWrapper.java:403)
at com.example.app.business.domain.validator.document.FileLegacyValidator.openPdf(FileLegacyValidator.kt:38)
at com.example.app.business.domain.validator.document.FileLegacyValidator.openPDFFileOrNull(FileLegacyValidator.kt:35)
at com.example.app.presentation.documents.DocumentViewModel$setStateEvent$1$1.invokeSuspend(DocumentViewModel.kt:36)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
This is not working: context.startActivity(intent)
Edit: Answer not relevant after your latest edit to question.
If you're going to use external storage, here's how to do it:
val isExternalStorageWritable =
(Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)
if (!isExternalStorageWritable ) {
// handle error: Storage not available
return
}
val rootFolder = context.getExternalFilesDir(null)
val downloadsFolder = File(rootFolder, "downloads")
val isDownloadsFolderCreated = downloadsFolder.mkdirs()
if (!isDownloadsFolderCreated) {
// handle error: unable to create folder
return
}
val tempInputStream = FileInputStream(tempFile)
val targetFile = File(downloadsFolder, "targetFileName.pdf")
val isTargetFileCreated = targetFile.createNewFile()
if (!isTargetFileCreated) {
// handle error: unable to create file
return
}
FileOutputStream(targetFile).use { outputStream ->
tempInputStream.copyTo(outputStream)
}
Make sure to call this from a background thread or IO coroutine.
I've fixed this problem. I always thought that my emulator had a PDF reader already installed (since the other emulator had it too), but it didn't. The activity couldn't be opened because there was no PDF reader.
Fix: Install PDF Reader.
So I'm making my first Android app and I'm trying to get it to allow the user to pick a video from their gallery before seeing the video and the video's current details in the next activity.
My problem is that when I use FFmpegMediaMetadataRetriever and pass it the video's filepath, I receive the error java.lang.IllegalArgumentException: setDataSource failed: status = 0xFFFFFFFF.
I've heard through the grapevine that this means my filepath is invalid. When I Log.d the filepath, I get content://media/external/file/3565, which to me looks like a proper filepath!
I hope somebody can help me figure this out.
Here is my activity class for context:
class NewProject : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_project)
val videoPath = intent.getStringExtra("video")
initVideo(videoPath)
backButtonText.setOnClickListener{ goBack() }
}
private fun goBack() {
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
}
private fun initVideo(videoPath:String) {
newProjVideoView.setVideoPath(videoPath)
newProjVideoView.start()
newProjVideoView.setOnCompletionListener {
newProjVideoView.pause()
}
getVideoMetadata(videoPath)
}
private fun getVideoMetadata(videoPath: String) {
try {
e("videoPath", videoPath)
val receiver = FFmpegMediaMetadataRetriever()
receiver.setDataSource(videoPath)
} catch (e:IOException) {
e("retrieve1","There was an issue", e)
}
}
}
I'm also happy to hear any constructive feedback on my code!
Please, thank you and have a nice day!
So, I think my issue stemmed from trying to pass the video through an intent and then running the MetadataRetriever. I solved it by getting all the info in the previous activity before passing each value as an extra to be used on the next screen.
I have an app that is not actually saving any images in anywhere besides the cache directory, just processing it and then discarding it from the cache dir after its processing is complete. I need to have the user be able to crop the image that was captured from the front-camera before processing. Is it possible to do this (sans implementing my own cropping method) without requesting the READ/WRITE_EXTERNAL_STORAGE permission? I have been checking out the Android Image Cropper library but it says that the user must enable the permission to open the Crop activity.
How I'm getting the image:
val tempImg = File.createTempFile("spectre_", "_tmpImg", context?.cacheDir)
val imgUri = Uri.parse(tempImg.path)
imageCapture.takePicture(
tempImg,
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
if (context != null) {
//this is from Android-Image-Cropper library
CropImage.activity(imgUri).start(context, frag)
}
}
override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
Toast.makeText(context, "Image could not be saved. Try again!", Toast.LENGTH_LONG).show()
}
}
)
I want to Update Profile Picture in my app. Every Time User updates his profile picture. But the Problem is Profile picture is only updating when i restart the app after clearing the app from the memory.
The code i want to implement automatically is placed in onCreate method.
And the uploaded pic will always be from the internal Storage. Code is Attached Bellow.
mImageView = findViewById(R.id.profile_Pic);
ContextWrapper cw = new ContextWrapper(getApplicationContext());
File directory = cw.getDir("imageDir", Context.MODE_PRIVATE);
File myImageFile = new File(directory, "my_image.jpeg");
Picasso.get().load(myImageFile).into(mImageView);
I want that the IMageView for the Profile pic is Always updated automatically when the user updates his/her dp.What Should i do or Where Should i place that code?
You can use Livedata to observe data changes and then update the view in the livedata's observer. If the internal storage was managed by Room, you could make it return a Livedata and go from there.
At a glance, I would do something like this (using Kotlin here, but I'm sure you'll understand):
Create a view model for the Activity, that would manage the Activity state. The view model would have a Livedata of type File:
class MyViewModel : ViewModel() {
private val dataAccess: SomeClassThatControlsDataAccess = ...
private val _myImageFile: MutableLiveData<File> = MutableLiveData()
val myImageFile: LiveData<File>
get() = _myImageFile
fun updateUserProfilePic() {
val image: File = dataAccess.getProfilePicFromStorage() // This method would encapsulate that file retrieval code you have on OnCreate
_myImageFile.value = image
}
// other stuff
}
Observe this in the Activity. Whenever the Activity is resumed and there's a change, the view will be updated:
class MyActivity : AppCompatActivity() {
private lateinit var mModel: MyViewModel
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other code to setup the activity...
// Get the ViewModel
mModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
// Observe the LiveData
mModel.myImageFile.observe(this, Observer {
Picasso.get().load(it).into(mImageView)
})
}
override fun OnResume() {
super.OnResume()
mModel.updateUserProfilePic()
}
}
If you don't want to use any Android framework stuff, you can use something like RxJava and follow a similar approach.
You can also probably just place that code on your OnResume method and be done with it, but that has a tendency of creating a coupled design that’s bug prone and hard to change and/or maintain.
How is the user updating their profile picture?. You could use Picasso each time the user updates their profile picture.
You could use Picasso in onActivityResult
*edit: added code sample
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == TAKE_PICTURE || requestCode == PICK_FROM_GALLERY) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// DO SOMETHING
Picasso.with(this).load(myImageFile).into(
}
}
}