OutOfMemoryException when reading large VideoFile to ByteArray - android

So after searching Stackoverflow for hours I tried loads of methods and library's but not ONE of them was the answer to my problem.
The problem is as follows:
val videoPath = getVideoPathFromURI(getSelectedVideoUri(requestCode, data))
val videoBytes = FileInputStream(File(videoPath)).use { input ->
input.readBytes()
}
This part is what I've had in my code before I found out about the bug.
Two of the many methods I tried are as follows:
#Throws(IOException::class)
private fun InputStream.readAllBytes(): ByteArray {
val bufLen = 4 * 0x400 // 4KB
val buf = ByteArray(bufLen)
var readLen: Int = 0
ByteArrayOutputStream().use { o ->
this.use { i ->
while (i.read(buf, 0, bufLen).also { readLen = it } != -1)
o.write(buf, 0, readLen)
}
return o.toByteArray()
}
}
private fun getByteArray(videoPath: String): ByteArray {
val inputStream = FileInputStream(File(videoPath))
val buffer = ByteArrayOutputStream()
var nRead: Int
val byteData = ByteArray(16384)
while (inputStream.read(byteData, 0, byteData.size).also { nRead = it } != -1) {
buffer.write(byteData, 0, nRead)
}
return buffer.toByteArray()
}
The way I obtain my video's is through either one of the following two intents:
private fun selectMultipleVideos() {
globalContext.checkPersmissions(Arrays.asList(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA)){ response ->
if(response){
showCameraChoiceDialog(options, DialogInterface.OnClickListener{ dialog, which ->
when(which){
0 -> {
val takeVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
startActivityForResult(takeVideoIntent, ACTION_TAKE_VIDEO)
}
1 -> {
val intent = Intent(Intent.ACTION_PICK)
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.setType("video/mp4")
startActivityForResult(intent, SELECT_VIDEOS)
}
}
})
}
}
}
My onActivityResult:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == RESULT_OK) {
if (requestCode == ACTION_TAKE_VIDEO) {
val videoUri = data!!.data
val filePath = getPath(globalContext, videoUri!!)
amountOfVideosUploaded++
val videoBytes = FileInputStream(File(filePath)).use { input -> input.readBytes() }
videoByteArray.add(LocalFile(filePath, videoBytes))
if (contentReminder.num_required_videos != amountOfVideosUploaded) {
if (localVideoArray.size != contentReminder.num_required_videos) {
localVideoArray.add("")
}
localVideoArray[amountOfVideosUploaded] = filePath
} else {
localVideoArray[0] = filePath
}
setupList()
videosDelivered.text = DataStorage.generalData.translations?.app__contentinbox__submit_video_label + " (" + amountOfVideosUploaded + "/" + contentReminder.num_required_videos + ")"
} else {
selectedVideos = getSelectedVideos(requestCode, data!!)
selectedVideo = getSelectedVideo(requestCode, data)
amountOfVideosUploaded++
val videoPath = getVideoPathFromURI(getSelectedVideoUri(requestCode, data))
val videoBytes = FileInputStream(File(videoPath)).use { input -> input.readBytes() }
videoByteArray.add(LocalFile(selectedVideo, videoBytes))
if (contentReminder.num_required_videos != amountOfVideosUploaded) {
if (localVideoArray.size != contentReminder.num_required_videos) {
localVideoArray.add("")
}
localVideoArray[amountOfVideosUploaded] = selectedVideos[i]
} else {
localVideoArray[0] = selectedVideos[i]
}
setupList()
videosDelivered.text = DataStorage.generalData.translations?.app__contentinbox__submit_video_label + " (" + amountOfVideosUploaded + "/" + contentReminder.num_required_videos + ")"
}
}
}
Some of my helper methods:
private fun getSelectedVideoUri(requestCode: Int, data: Intent): Uri {
return data.data!!
}
private fun getSelectedVideo(requestCode: Int, data:Intent): String {
var result : String = ""
val videoURI = data.data!!
val filePath = getPath(globalContext, videoURI)
result = filePath
return result
}
private fun getSelectedVideos(requestCode: Int, data:Intent): MutableList<String> {
val result : MutableList<String> = mutableListOf()
val videoURI = data.data!!
val filePath = getPath(globalContext, videoURI)
result.add(filePath)
return result
}
If it does NOT get an error I upload it to the server with the following method:
fun uploadVideoFiles(module: String, file: LocalFile, completionHandler: (path: String) -> Unit){
val params = HashMap<String, String>()
params["module"] = module
params["action"] = "uploadFiles"
val request = object : MultipartRequest(Method.POST, Router.getUploadUrl(), Response.Listener { response ->
try {
completionHandler(Gson().fromJson(String(response.data), FileResponse::class.java).data.file_path)
} catch (ignore: Exception){
showSnackbar("Er is iets fout gegaan", ERROR)
}})
{
override fun getParams(): MutableMap<String, String> {
return params
}
override fun getByteData(): MutableMap<String, DataPart> {
val attachment = HashMap<String, DataPart>()
val extension = ".mp4"
attachment["files"] = DataPart(generateCustomString() + extension, file.byteArray!!, MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.replace(".", ""))!!)
return attachment
}
}
request.retryPolicy = DefaultRetryPolicy(600000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
volleyHelper.addToRequestQueue(request)
}
Now the error I get (for each of the methods shown + my own / and all the other methods I tried):
2021-08-13 11:57:19.726 31911-31911/nl.mycontent E/AndroidRuntime: FATAL EXCEPTION: main
Process: nl.mycontent, PID: 31911
java.lang.OutOfMemoryError: Failed to allocate a 268435472 byte allocation with 25165824 free bytes and 124MB until OOM, target footprint 163389616, growth limit 268435456
at java.util.Arrays.copyOf(Arrays.java:3161)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
at nl.mycontent.controller.video.VideoDetail.readAllBytes(VideoDetail.kt:199)
at nl.mycontent.controller.video.VideoDetail.onActivityResult(VideoDetail.kt:169)
at androidx.fragment.app.FragmentActivity.onActivityResult(FragmentActivity.java:170)
at android.app.Activity.dispatchActivityResult(Activity.java:8300)
at android.app.ActivityThread.deliverResults(ActivityThread.java:5353)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:5401)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:8167)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
Now I'm hoping there is anyone who can tell me for all Android Versions (since each version has different access requirements for external storage and stuff nowadays) how I can read a file without getting the OOM exception but also most importantly WHY the solution works and WHERE it went wrong on my end.

If you're only trying to upload the video files, why not use a custom RequestBody which means you're not reading the entire file into memory? Like so:
class InputStreamRequestBody(
private val contentResolver: ContentResolver,
private val uri: Uri) : RequestBody()
{
override fun contentType(): MediaType? {
val contentType = contentResolver.getType(uri)
return contentType?.toMediaTypeOrNull()
}
override fun writeTo(sink : BufferedSink)
{
val input = contentResolver.openInputStream(uri)
input?.use { sink.writeAll(it.source()) }
?: throw IOException("Could not open URI")
}
}
Then in onActivityResult():
val reqBody = InputStreamRequestBody(requireContext().contentResolver, videoFileUri)
viewModel.uploadVideo(reqBody)
Then, depending on design pattern, upload your video in lets say a ViewModel:
fun uploadVideo(video: RequestBody) {
viewModelScope.launch(Dispatchers.IO + someExceptionHandler) {
val videoRequestBody = MultipartBody.Part.createFormData("video", "some_video", video)
val response = someRetrofitService.uploadVideo(videoRequestBody)
}
}
Where the Retrofit service method looks something like this:
#Multipart
#POST("api/v1/files/videos") suspend fun uploadVideo(
#Part video: MultipartBody.Part? = null)

Related

Installing an APK Programmatically (Android 12)

I'm attempting to install an APK programmatically on Android 12 but seem to be running into unknown issues at this point. All advice I've found regarding installing an APK programmatically seem to be deprecated.
Currently, I'm able to save my file but whenever I attempt to install it using PackageManager.PackageInstaller, it fails silently and I'm unable to find anything in the logs suggesting what the failure might've been.
Here's my package installer object.
object PackageInstaller {
#SuppressLint("WrongConstant")
#Throws(IOException::class)
fun installPackage(
context: Context,
installSessionId: String?,
packageName: String?,
apkStream: InputStream?
) {
val packageManger = context.packageManager
val packageInstaller = packageManger.packageInstaller
val params = android.content.pm.PackageInstaller.SessionParams(
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
params.setAppPackageName(packageName)
var session: android.content.pm.PackageInstaller.Session? = null
try {
val sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
val out = session.openWrite(installSessionId!!, 0, -1)
val buffer = ByteArray(1024)
var length: Int
var count = 0
if (apkStream != null) {
while (apkStream.read(buffer).also { length = it } != -1) {
out.write(buffer, 0, length)
count += length
}
}
session.fsync(out)
out.close()
val intent = Intent
intent.addFlags(Intent.ACTION_PACKAGE_ADDED)
Log.v("installer", "Installing..?")
session.commit(
PendingIntent.getBroadcast(
context, sessionId,
intent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
).intentSender
)
}finally {
session?.close()
}
}
}
At this point I'm pretty lost as to where to look next. Does anyone even know if this is still possible? Or a solution to this issue?
You can try with this it is working with android 12
class DownloadApk(private var context: WeakReference<Context>) {
#JvmOverloads
fun startDownloadingApk(url: String, fileName: String = "App Update") {
if (URLUtil.isValidUrl(url)) {
DownloadNewVersion(context, url, fileName).execute()
}
}
#Suppress("DEPRECATION")
private class DownloadNewVersion(
private val context: WeakReference<Context>,
val downloadUrl: String,
val fileName: String
) : AsyncTask<String, Int, Boolean>() {
private lateinit var bar: ProgressDialog
override fun onPreExecute() {
super.onPreExecute()
bar = ProgressDialog(context.get()).apply {
setCancelable(false)
setMessage("Downloading...")
isIndeterminate = true
setCanceledOnTouchOutside(false)
show()
}
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
var msg = ""
val progress = values[0]
if (progress != null) {
bar.progress = progress
msg = if (progress > 99) "Finishing... " else "Downloading... $progress%"
}
bar.apply {
isIndeterminate = false
max = 100
setMessage(msg)
}
}
override fun onPostExecute(result: Boolean?) {
super.onPostExecute(result)
bar.dismiss()
if (result != null && result) {
context.get()?.let {
Toast.makeText(it, "Update Done", Toast.LENGTH_SHORT).show()
}
} else {
context.get()?.let {
Toast.makeText(it, "Error: Try Again", Toast.LENGTH_SHORT).show()
}
}
}
override fun doInBackground(vararg p0: String?): Boolean {
var flag = false
try {
val path =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + "/"
var outputFile = File("$path$fileName.apk")
var repetition = 1
while (outputFile.exists()) {
outputFile = File("$path$fileName ($repetition).apk")
repetition++
}
val directory = File(path)
if (!directory.exists()) {
directory.mkdirs()
}
val url = URL(downloadUrl)
val c = url.openConnection() as HttpURLConnection
c.requestMethod = "GET"
c.connect()
val fos = FileOutputStream(outputFile)
val inputStream = c.inputStream
val totalSize = c.contentLength.toFloat() //size of apk
val buffer = ByteArray(1024)
var len1: Int
var per: Float
var downloaded = 0f
while (inputStream.read(buffer).also { len1 = it } != -1) {
fos.write(buffer, 0, len1)
downloaded += len1
per = (downloaded * 100 / totalSize)
publishProgress(per.toInt())
}
fos.close()
inputStream.close()
openNewVersion(outputFile.path)
flag = true
} catch (e: MalformedURLException) {
Log.e("DownloadApk", "Update Error: " + e.message)
flag = false
} catch (e: IOException) {
e.printStackTrace()
}
return flag
}
private fun openNewVersion(location: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
getUriFromFile(location),
"application/vnd.android.package-archive"
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.get()?.startActivity(intent)
}
private fun getUriFromFile(filePath: String): Uri? {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Uri.fromFile(File(filePath))
} else {
context.get()?.let {
FileProvider.getUriForFile(
it,
it.packageName + ".provider",
File(filePath)
)
}
}
}
}
}

Adding user profile picture in Kotlin

I've recently started programming in Kotlin and cannot seem to add a profile picture to a user when registering it.
According to the code here, I can access to the gallery and retrieve the image information. The picture will appear on screen, but after registering the user the image url will not appear anywhere.
class RegisterUser : AppCompatActivity() {
private val database = FirebaseDatabase.getInstance()
private val auth: FirebaseAuth = FirebaseAuth.getInstance()
private val UserCreation = database.getReference("Usuarios")
private val pickImage = 100
private var imageUri: Uri? = null
lateinit var imageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register_user)
Goback.setOnClickListener {
val Gobackou = Intent(this, MainActivity::class.java)
startActivity(Gobackou)
}
RegisterConfirm.setOnClickListener {
val SetUser = SetUser.text.toString()
val SetPass = setPass.text.toString()
val SetEmail = SetEmail.text.toString()
if (SetUser.isEmpty() && SetPass.isEmpty() && SetEmail.isEmpty()) {
Toast.makeText(this, "Faltan Campos", Toast.LENGTH_SHORT).show()
} else {
RegisterUserv2(SetEmail, SetPass, SetUser)
}
}
selectPP.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(intent, pickImage)
}
}
var selectedPhotoUri: Uri? = null
//guardar la foto de perfil
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == pickImage) {
val selectedPhotoUri = data?.data
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedPhotoUri)
val bitmapDrawable = BitmapDrawable(bitmap)
userimg.setBackgroundDrawable(bitmapDrawable)
}
}
private fun RegisterUserv2(email: String, password: String, user: String) {
auth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
uploadimage()
UltrasaveUsuario(Usuarios(auth.currentUser!!.uid, user, password, email))
val Gobackou = Intent(this, MainActivity::class.java)
startActivity(Gobackou)
} else {
Toast.makeText(this, "Registro ERROR", Toast.LENGTH_LONG).show()
}
}
}
private fun UltrasaveUsuario(usuario: Usuarios) {
val mensajeFirebase = UserCreation.push()
usuario.id = mensajeFirebase.key ?: ""
mensajeFirebase.setValue(usuario)
}
private fun uploadimage(imageurl: String){
if (selectedPhotoUri == null) return
val filename = UUID.randomUUID().toString()
val ref = FirebaseStorage.getInstance().getReference("/images/$filename/")
ref.putFile(selectedPhotoUri!!)
.addOnSuccessListener {
ref.downloadUrl.addOnSuccessListener{
}
}
}
}
Since you are using firebase I will show you how to save firebase storage.
private fun uploadImage() {
if (mSelectedImageFileUri != null) {
val sRef: StorageReference = FirebaseStorage.getInstance().reference.child(
"users/ $uid/profile.jpg"
)
sRef.putFile(mSelectedImageFileUri!!)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { url ->
}
}.addOnFailureListener {
//error
}
} else {
}
}
then you will take this picture using the user id to take .
firebase>storage to view the image
getImage
val sRef: StorageReference = FirebaseStorage.getInstance().reference.child(
"users/ $uid/profile.jpg"
)
sRef.downloadUrl.addOnSuccessListener {
Picasso.get().load(it).into(globalprofileImage)
}
Build.gradle
implementation 'com.squareup.picasso:picasso:2.71828'
https://www.youtube.com/watch?v=nNYLQcmB7AU&t=449s&ab_channel=SmallAcademy

How to POST multiple files in one Request using VolleyMutlipart in Android Studio?

I have a function that takes file name and its Uri then send that file to the server.
Here it is
private fun uploadFile(pdfname: String, anyfile: Uri) {
var iStream: InputStream? = null
val myurl = "https://apiurl"
var rQueue = Volley.newRequestQueue(this)
try {
iStream = contentResolver.openInputStream(anyfile)
val inputData = getBytes(iStream)
val volleyMultipartRequest: VolleyMultipartRequest = object : VolleyMultipartRequest(
Method.POST, myurl,
Response.Listener { response ->
try {
val jsonobject = JSONObject(String(response.data))
val returnmsg = jsonobject.getString("hua ki nhi")
if(returnmsg == "aish kro ho gya")
{
Toast.makeText(this, "File Uploaded Successfully", Toast.LENGTH_SHORT).show()
}
else{
Toast.makeText(this, "File Not Uploaded", Toast.LENGTH_SHORT).show()
}
dialog.cancel()
}catch (e: JSONException){
e.printStackTrace()
dialog.cancel()
}
},
Response.ErrorListener { error ->
Toast.makeText(
applicationContext,
error.message,
Toast.LENGTH_SHORT
).show()
dialog.cancel()
}) {
#Throws(AuthFailureError::class)
// override fun getParams(): Map<String, String>? {
// // params.put("tags", "ccccc"); add string parameters
// return HashMap()
// }
override fun getByteData(): Map<String, DataPart> {
val params: MutableMap<String, DataPart> = HashMap()
params["evidence_image"] = DataPart(pdfname, inputData)
return params
}
}
volleyMultipartRequest.retryPolicy = DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
rQueue.add(volleyMultipartRequest)
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
#Throws(IOException::class)
fun getBytes(inputStream: InputStream?): ByteArray {
val byteBuffer = ByteArrayOutputStream()
val bufferSize = 1024
val buffer = ByteArray(bufferSize)
var len = 0
while (inputStream!!.read(buffer).also { len = it } != -1) {
byteBuffer.write(buffer, 0, len)
}
return byteBuffer.toByteArray()
}
But I have to pass the Uri 1 by 1 to upload to the server. That means I have to call for API every time to upload multiple files.
This is my code where I take multiple files from the user.
attachbtn.setOnClickListener {
val mimeTypes = arrayOf("image/*", "application/pdf")
val intent = Intent()
intent.type = "image/* | application/pdf"
intent.action = Intent.ACTION_GET_CONTENT
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
startActivityForResult(Intent.createChooser(intent, "Choose Picture"), 111)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 111 && resultCode == Activity.RESULT_OK) {
if (data?.clipData != null) {
Toast.makeText(this, "Multiple file selected", Toast.LENGTH_SHORT).show()
val mulfile = data.clipData!!
val itemcount = mulfile.itemCount
}
}
}
I have already Included VolleyMultipartRequest Class.
Please tell me a Way where I can send Multiple Files in one Request.
Thankyou.

Kotlin - Firebase - Save Uri to user's folder images keeps saving wrong Uri

I'm trying to build an Instagram-like app, an issue occurs when I try to save taken photo to user's folder "images" on Firebase database. It keeps saving something like "com.google.firebase.storage.UploadTask#62873ce"
Instead of link like this:
"https://firebasestorage.googleapis.com/v0/b/gramgram-1b3f9.appspot.com/o/users%2F7q3pqR6GnHMx7NSdgoBqZETkrS32%2Fphoto?alt=media&token=bbcbe556-1de5-4176-aba2-599f829e65"
here is my Share Activity.kt
class ShareActivity : BaseActivity(2) {
private val TAG = "ShareActivity"
private lateinit var mCamera: CameraHelper
private lateinit var mFirebase: FirebaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share)
Log.d(TAG, "onCreate")
mFirebase =FirebaseHelper(this)
mCamera = CameraHelper(this)
mCamera.takeCameraPicture()
back_image.setOnClickListener{finish()}
share_text.setOnClickListener{share()}
}
#SuppressLint("MissingSuperCall")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == mCamera.REQUEST_CODE){
if (resultCode == RESULT_OK){
GlideApp.with(this).load(mCamera.imageUri).centerCrop().into(post_image)
} else{
finish()
}
}
}
private fun share(){
val imageUri = mCamera.imageUri
if (imageUri != null){
val uid = mFirebase.auth.currentUser!!.uid
mFirebase.storage.child("users").child(uid).child("images")
.child(imageUri.lastPathSegment!!).putFile(imageUri).addOnCompleteListener {
if (it.isSuccessful){
mFirebase.database.child("images").child(uid).push()
.setValue(it.toString())
.addOnCompleteListener{
if (it.isSuccessful){
startActivity(Intent(this,
ProfileActivity::class.java))
finish()
} else {
showToast(it.exception!!.message!!)
}
}
} else {
showToast(it.exception!!.message!!)
}
}
}
}
}
here are my uploadUserPhoto and updateUsePhoto functions in FirebaseHelper.kt
fun uploadUserPhoto(photo: Uri, onSuccess: (String) -> Unit) {
val uTask = storage.child("users/${auth.currentUser!!.uid}/photo").putFile(photo)
storage.child("users/${auth.currentUser!!.uid}/photo").putFile(photo)
.addOnCompleteListener {
if (it.isSuccessful) {
uTask.continueWithTask { _ ->
storage.child("users/${auth.currentUser!!.uid}/photo").downloadUrl
}.addOnCompleteListener{
if (it.isSuccessful && it.result != null) {
onSuccess(it.result.toString())
} else {
activity.showToast(it.exception!!.message!!)
}
}
}
}
}
fun updateUserPhoto( photoUrl: String, onSuccess: () -> Unit){
database.child("users/${auth.currentUser!!.uid}/photo").setValue(photoUrl)
.addOnCompleteListener {
if (it.isSuccessful) {
onSuccess()
} else {
activity.showToast(it.exception!!.message!!)
}
}
}
I am not sure how to set my private fun share() to upload correct URL to User's "images" folder
and here is my CameraHelper.kt
class CameraHelper(private val activity: Activity){
var imageUri: Uri? = null
val REQUEST_CODE = 1
private val simpleDateFormat = SimpleDateFormat(
"yyyyMMdd_HHmmss",
Locale.US
)
fun takeCameraPicture() {
val intent =
Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (intent.resolveActivity(activity.packageManager) != null) {
val imageFile = createImageFile()
imageUri = FileProvider.getUriForFile(
activity,
"com.example.homeactivity.fileprovider",
imageFile
)
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
activity.startActivityForResult(intent, REQUEST_CODE)
}
}
private fun createImageFile(): File {
val storageDir: File? = activity.getExternalFilesDir(
Environment.DIRECTORY_PICTURES
)
return File.createTempFile(
"JPEG_${simpleDateFormat.format(Date())}_",
".jpg",
storageDir
)
}
}
You're not using the result from putFile(imageUri) correctly. You have:
putFile(imageUri).addOnCompleteListener {
if (it.isSuccessful){
mFirebase.database.child("images").child(uid).push()
.setValue(it.toString())
That it.toString() is not the URL. it is the UploadTask object returned by putFile(). You can't just turn that into a string - it's a Task that you need to observe to get the result.
You seem to be fetching a download URL correctly in uploadUserPhoto using downloadUrl to fetch the URL to write. You're going to have to do it that way instead.
See also the documentation and this question.
I managed to solve it.
In case anyone needs it, here is working function share():
private fun share(){
val imageUri = mCamera.imageUri
if (imageUri != null){
val uid = mFirebase.auth.currentUser!!.uid
val uTask = mFirebase.storage.child("users").child(uid).child("images").child(imageUri.lastPathSegment!!).putFile(imageUri)
mFirebase.storage.child("users").child(uid).child("images").child(imageUri.lastPathSegment!!).putFile(imageUri)
.addOnCompleteListener {
if (it.isSuccessful) {
uTask.continueWithTask { _ ->
mFirebase.storage.child("users").child(uid).child("images")
.child(imageUri.lastPathSegment!!).downloadUrl
}.addOnCompleteListener {
if (it.isSuccessful && it.result != null)
mFirebase.database.child("images").child(uid).push()
.setValue(it.result.toString())
.addOnCompleteListener {
if (it.isSuccessful) {
startActivity(
Intent(
this,
ProfileActivity::class.java
)
)
finish()
} else {
showToast(it.exception!!.message!!)
}
}
}
} else {
showToast(it.exception!!.message!!)
}
}
}
}

Using ACTION_IMAGE_CAPTURE get full image bitmap kotlin

All I want to do is allow users to take a photo and then after they've finished filling out a form submit that photo and upload to server.
I think I need the image in bitmap form so I can convert to Base64 string.
I've tried multiple different things I've found but none of them are working. I either get directory errors or intent null errors.
class Register : AppCompatActivity(), View.OnClickListener {
var PlayerID = ""
var mCurrentPhotoPath = ""
var stuff: File? = null
private var filePath: Uri? = null
private val PICK_IMAGE_REQUEST = 1
#RequiresApi(Build.VERSION_CODES.O)
override fun onClick(v: View?) {
if(v === buttonLoadPicture)
showFileChooser()
else if (v == btn_register)
uploadFile()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
if (resultCode != RESULT_CANCELED) {
if (requestCode == PICK_IMAGE_REQUEST) {
val thumbnail = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath)
PhotoImageView.setImageBitmap(thumbnail)
filePath = getRealPathFromURI(Uri.parse(filePath.toString()))
}
}
}
//////////////
fun getImageUri(inContext: Context , inImage: Bitmap): Uri {
val bytes = ByteArrayOutputStream()
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes)
val path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null)
return Uri.parse(path)
}
fun getRealPathFromURI(uri: Uri): Uri? {
val proj = arrayOf(MediaStore.Images.Media.DATA)
val cursor = getContentResolver().query(uri, proj, null, null, null)
cursor.moveToFirst()
val idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
return Uri.parse(cursor.getString(idx))
}
////////////////
#RequiresApi(Build.VERSION_CODES.N)
private fun showFileChooser() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA),
1)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
1)
}
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, 1)
}
val jsonobject = JSONObject()
#RequiresApi(Build.VERSION_CODES.O)
private fun uploadFile() {
val progress: ProgressBar = progressBarR
progress.visibility= View.VISIBLE
val iStream = contentResolver.openInputStream(filePath)
val bitmap = BitmapFactory.decodeStream(iStream)
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val m = baos.toByteArray()
val imageString = Base64.encodeToString(m,Base64.DEFAULT)
//get form data from register layout
jsonobject.put("FirstName", input_fname.text)
jsonobject.put("LastName", input_lname.text)
jsonobject.put("UserName", input_username.text)
jsonobject.put("Phone", input_phone.text)
jsonobject.put("DOB", input_DOB.text)
jsonobject.put("Photo", imageString)
jsonobject.put("Profile", input_profile.text)
jsonobject.put("Email", input_email.text)
jsonobject.put("Password", input_password.text)
jsonobject.put("ConfirmPassword", input_confirm.text)
var url = "https://elimination.azurewebsites.net/api/Account/Post"
val que = Volley.newRequestQueue(this#Register)
val req = JsonObjectRequest(Request.Method.POST, url, jsonobject,
Response.Listener<JSONObject>{
response -> response.toString()
//save PlayerID to val PlayerID
PlayerID = response.get("PlayerID").toString()
//save to sharedPreferences
val email = input_email.text.toString()
val mypref = getSharedPreferences(email, Context.MODE_PRIVATE)
val editor = mypref.edit()
editor.putString(email, PlayerID)
editor.apply()
val intent = Intent(this, Login::class.java)
startActivity(intent)
},
Response.ErrorListener{
response ->
Log.e("Something went wrong", response.toString())
})
//if server is slow
val socketTimeout = 30000 // 30 seconds
val policy = DefaultRetryPolicy(socketTimeout,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
req.setRetryPolicy(policy)
// Adding request to request queue
que.add(req)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
buttonLoadPicture.setOnClickListener(this)
btn_register.setOnClickListener(this)
}
}
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, 1)
You are invoking ACTION_IMAGE_CAPTURE without EXTRA_OUTPUT.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
if (resultCode != RESULT_CANCELED) {
if (requestCode == PICK_IMAGE_REQUEST) {
val thumbnail = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath)
PhotoImageView.setImageBitmap(thumbnail)
filePath = getRealPathFromURI(Uri.parse(filePath.toString()))
}
}
}
You are not using the Bitmap that you get back from ACTION_IMAGE_CAPTURE when you do not supply EXTRA_OUTPUT. Instead, you are looking in a filePath value that may well be null and certainly has nothing to do with the photo that the user's chosen camera app may have taken.
If you want to tell the camera app where to put a full-size photo, use EXTRA_OUTPUT. However, then, bear in mind that you are unlikely to be able to convert a full-size photo to base64, as you will run out of memory.

Categories

Resources