MediaScannerConnection.scanFile() returning null uri - android

I'm trying to save a file as PNG of a canvas where the user can draw something and then call an Intent.ACTION_SEND so that the user can share it's drawing with other apps.
The code is able to save the file without any problems, but when I try to use the MediaScannerConnection.scanFile(), the Uri returned by the function is null. I'm using the absolute path of the file created, so I can't understand why this is happening.
My class, called BitmapAsyncTask inherits from AsyncTask (yes, I know it's deprecated). Here's the important code:
Writing the file to memory:
override fun doInBackground(vararg p0: Any?): String {
var result = ""
try {
val bytes = ByteArrayOutputStream()
mBitmap.compress(Bitmap.CompressFormat.PNG, 95, bytes)
val file = File(externalCacheDir!!.absoluteFile.toString()
+ File.separator + "KidsDrawingApp_"
+ System.currentTimeMillis() / 1000 + ".png")
val fileOutput = FileOutputStream(file)
fileOutput.write(bytes.toByteArray())
fileOutput.close()
result = file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
}
Log.d("File", result)
return result
}
** The mBitmap variable is just the Bitmap generated from the canvas.
Here, the Log.d returns, for instance:
D/File: /storage/emulated/0/Android/data/com.example.kidsdrawingapp/cache/KidsDrawingApp_1599992654.png
I can access the file just fine if I open the Files app and search for it.
But when I run the MediaScannerConnection on onPostExecute(), the function doesn't return an uri based on the absolute path at all. Here's the code:
MediaScannerConnection.scanFile(this#MainActivity, arrayOf(result), null) {
path, uri -> val shareIntent = Intent()
Log.d("Path", path)
Log.d("Uri", uri.toString())
shareIntent.action = Intent.ACTION_SEND
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
shareIntent.type = "image/png"
startActivity(
Intent.createChooser(
shareIntent, "Share image"
)
)
}
Once again, the Log.d("Path", path) returns the same file as the previous Log.d(), but when I try to convert the Uri to string, it crashes because it's null.
I tried adding "file://" + file.absolutePath" like I saw in some other answers but it still didn't work, the uri returned by the scanFile() was still null.
I'm using API 21.
File Provider Code
AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.kidsdrawingapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/path" />
</provider>
#xml/path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="captured" path="Android/data/com.example.kidsdrawingapp/files" />
</paths>

I can't seem to figure out why it can't return a valid uri if the file is being saved in the phone and the path is a valid one
It is valid. However, it is not indexable by MediaStore on Android 10 and higher. MediaStore will no longer index files in app-specific external storage, such as getExternalFilesDir(), which is what you are using.
If your objective is to have the image be usable by every app on the device, then getting indexed by MediaStore is fine. On Android 10+, you can insert() into the MediaStore and use the resulting Uri for writing out your content. See this sample app for a demonstration, though in my case I am writing out a video, not a PNG.
If, instead, all you want to do is share this content, then do not use MediaScannerConnection. Instead, use FileProvider. See the documentation and this sample app (though in my case I am sharing a PDF, not a PNG).

... in case the above solution was not fully clear to everyone - here's how I applied the suggested fix to the reported file sharing issue within the tutorial exercise "Kids Drawing App" (from "The Complete Android 10 & Kotlin Development Masterclass" at Udemy):
// offer to share content
MediaScannerConnection.scanFile(
this#MainActivity,
arrayOf(result),
null
) { path, _ ->
// Use the FileProvider to get a content URI
val requestFile = File(path)
val fileUri: Uri? = try {
FileProvider.getUriForFile(
this#MainActivity,
AUTHORITY,
requestFile)
} catch (e: IllegalArgumentException) {
Log.e("File Selector",
"The selected file can't be shared: $requestFile")
null
}
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "image/png"
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
startActivity(
Intent.createChooser(
shareIntent, "Share"
)
)
}
... where I added the following AUTHORITY definition:
// static variables
companion object {
private const val STORAGE_PERMISSION_CODE = 1
private const val GALLERY = 2
private const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileprovider"
}

Related

How to write a JSON file to a specific location using the Media Store API in Android

Having invoked a directory selector on Android with:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
)
activity?.startActivityForResult(intent, REQUEST_CODE_FOLDER_PERMISSION)
And having obtained the URI of said route in onActivityResult(), being the URI of the form (example in case of having chosen a folder named backup in the root of the external storage):
content://com.android.externalstorage.documents/tree/primary:backup
At this point, how do you write a file to that location? After researching various answers on how to write files using the Media Store API, all the examples I've seen use constants to refer to already existing media directories, but in my case I want to create a new document (which is a JSON file) in the directory chosen by the user.
You will not use the MediaStore to save a file if you obtained an uri using ACTION_OPEN_DOCUMENT_TREE to get permission for a folder.
Just continue to use Storage Access Framework and implement DocumentFile.createFile().
Pretty basic exercise for learning SAF.
If you want to use the MediaStore to save a file then you do not have the user to select a folder first.
Thanks to #CommonsWare for pointing me in the right direction:
var outputStream: OutputStream? = null
try {
val uri = Uri.parse(path)
val document = DocumentFile.fromTreeUri(context, uri)
val file = document?.createFile(mimeType, filename)
?: throw Exception("Created file is null, cannot continue")
val fileUri = file.uri
val contentResolver = context.contentResolver
outputStream = contentResolver.openOutputStream(fileUri)
val bytes = content.toByteArray()
outputStream?.write(bytes)
outputStream?.flush()
} catch (e: Exception) {
// Handle error
} finally {
outputStream?.close()
}

How to save a file to shared Document folder on Android without using the file picker

I want to save a csv file in the Document folder on Android (or Download if Document can't allow this). I have the file content in a variable hence that's not the problem.
Official Documentation use a new activity :
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
putExtra(Intent.EXTRA_TITLE, "invoice.pdf")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, CREATE_FILE)
For now I tried the code below but it's in the app specific storage and I can't find it in my document :
val file = File(path, filename)
Timber.w("Filename : ${file.absolutePath}")
try {
val fileOutPutStream = FileOutputStream(file)
fileOutPutStream.write(content.toByteArray())
fileOutPutStream.close()
} catch (e: IOException) {...}
The log is : /storage/emulated/0/Android/data/com.example.mypackage/files/Documents/myfile.csv
How should I proceed?
You do not have to let the user pick something first.
You can directly create your file in Download or Document folder.
Or in other public folders.

Video will not show up in Photos on Android 24 using FileProvider and MediaScan

I am kind of stuck on this one and have been working on it for a little while with no luck. I have an android application which allows the user to take photos/videos. The files are stored on external storage using a FileProvider. I have been trying (without luck) to get the videos to show up in the Photos app and have not been able to. The Intent.ACTION_MEDIA_SCANNER_SCAN_FILE and sendBroadcast works perfect for images but does not work on video. I know the videos are being captured, they are being saved in the correct location, and are not corrupted (i can play them in VideoView using FileProvider content Uri) I have not attempted to update this to Android 10 yet as my minimum target is 24 and i cannot seem to get it to work for that.
Creating the Intent.ACTION_MEDIA_SCANNER_SCAN_FILE and sendBroadcast the video does not show up. If i use MediaScannerConnection it does not show up. If i manually insert with the content resolver using the content URI it shows up in Photos but video is not playable and there is no preview. If i manually insert into the content resolver with the absolute path it does not show up at all in Photos
Below is the code i use to take the video and add to the MediaStore
File Provider
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
File Provider Paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="Image" path="Image" />
<external-files-path name="Recording" path="Recording"/>
<external-files-path name="Video" path="Video"/>
</paths>
Methods to create the file and call Intent to capture video
private fun takeVideo() {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also{ videoIntent ->
val videoFile: File? = try{
createVideoFile()
}catch(ex: IOException){
Toast.makeText(mMainActivityHelper.contextProvider(), R.string.error_taking_photo, Toast.LENGTH_SHORT).show()
Log.w("OPENING_VIDEO_FILE", "Error opening video file with message = " + ex.message)
return
}
mFilePath = Uri.parse(videoFile?.absolutePath)
videoFile?.also{
videoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
mFileUri = FileProvider.getUriForFile(mMainActivityHelper.contextProvider(), "FileProvider", it)
videoIntent.putExtra(MediaStore.EXTRA_OUTPUT, mFileUri)
mMainActivityHelper.startActivityForResult(videoIntent, MainActivity.TAKE_VIDEO)
}
}
}
#Throws(IOException::class)
private fun createVideoFile(): File? {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val externalDir = mMainActivityHelper.externalFilesDirProvider(null)
val imagePath = File(externalDir, "NuNoteVideo")
if(!imagePath.exists())
imagePath.mkdir()
return File(imagePath.path, "MPEG_${timeStamp}" + ".mp4")
}
OnActivityResult code i have tried and none of it has worked. I have tried with both the content URI and the absolute path with no luck
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when{
requestCode == TAKE_VIDEO && resultCode == Activity.RESULT_OK->{
val cv = ContentValues(3).apply {
val absolutePath = myObjectWithThePath.mFilePath.toString()
val name = absolutePath.substring(absolutePath.lastIndexOf("/") + 1)
put(MediaStore.Video.Media.TITLE, name)
put(MediaStore.Video.Media.DISPLAY_NAME, name)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.DATE_ADDED, Date().time)
put(MediaStore.MediaColumns.DATA, absolutePath)
contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, this)
}
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val fileUri = myObjectWithThePath.mFileUri
if(fileUri != null) {
val f = File(fileUri)
mediaScanIntent.data = Uri.fromFile(f)
//mediaScanIntent.type = "video/mp4"
sendBroadcast(mediaScanIntent)
}
}
val f = File(myObjectWithThePath.mFilePath.toString())
MediaScannerConnection.scanFile(applicationContext, listOf(f.absolutePath).toTypedArray(), listOf("video/mp4").toTypedArray()){
path, uri ->
print(path)
print(uri) //This shows a URI if i use the absolute path but nothing shows up in the photos app. If i use content uri with FileProvider is null
}
I have looked at many many many anwsers on Stack already and nothing has worked. Any help is greatly appreciated
Video not shown on Gallery
MediaScanner and ScopedStorage on SDK-29
MediaStore.ACTION_VIDEO_CAPTURE not saving the video in Nougat 7.0
ACTION_MEDIA_SCANNER_SCAN_FILE from external sdcard Lollipop+
Saving photos and videos using Android FileProvider to the gallery
android - save image into gallery
MediaScannerConnection doesn't work
Android How to use MediaScannerConnection scanFile
Android MediaStore insertVideo
Capturing and Saving Videos in Android (API > 24) using File object?
Posting an update. I solved this by creating a video without providing a file path. This creates the video in the DCIM folder. When the video is created I save the uri, and query the uri to make sure it still exists on the load of the application. Not exactly what i wanted but it will works

AndroidResultContracts.TakePicture() returns Boolean instead of Bitmap

The contract has been changed to return Boolean instead of Bitmap starting in androidx.activity version 1.2.0-alpha05. How can I use the Boolean returned by the built in AndroidResultContracts.TakePicture() contract to access and display the photo just taken by the user?
I am using
implementation 'androidx.activity:activity-ktx:1.2.0-alpha07'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha07'
Here's my full sample code showing how to use the built-in Android Result Contract to take a photo from your application and display it in an ImageView.
Note: My solution uses View Binding
MainActivity's layout XML included (1) a button defining onTakePhotoClick as the onClick event and (2) and ImageView to display the photo taken.
<Button
android:id="#+id/take_photo_button"
style="#style/Button"
android:drawableStart="#drawable/ic_camera_on"
android:onClick="onTakePhotoClick"
android:text="#string/button_take_photo"
app:layout_constraintTop_toBottomOf="#id/request_all_button" />
...
<ImageView
android:id="#+id/photo_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="#id/take_video_button" />
In my MainActivity I have done the following:
Defined imageUri: Uri? which will be set to the uri of the image taken by the TakePicture() contract.
Implemented onTakePhotoClick() to check for the necessary camera permissions before launching the TakePicture() contract.
Defined takePictureRegistration: ActivityResultLauncher which will actually launch the request to take a photo on the device. When isSuccess is returned as true then I know the imageUri I previously defined now references the photo I just took.
Defined a takePicture: Runnable simply for code reuse. Note that the 2nd String parameter passed to the FileProvider.getUriForFile(context, authority, file) method will need to match the authorities attribute provided to the <provider> in your app's AndroidManifest.xml.
For full transparency, I have also added the code showing how I use the ActivityResultContracts.RequestPermission() to request the user for runtime permissions to access the camera.
private var imageUri: Uri? = null
/**
* Function for onClick from XML
*
* Check if camera permission is granted, and if not, request it
* Once permission granted, launches camera to take photo
*/
fun onTakePhotoClick(view: View) {
if (!checkPermission(Manifest.permission.CAMERA)) {
// request camera permission first
onRequestCameraClick(callback = takePicture)
} else {
takePicture.run()
}
}
private val takePicture: Runnable = Runnable {
ImageUtils.createImageFile(applicationContext)?.also {
imageUri = FileProvider.getUriForFile(
applicationContext,
BuildConfig.APPLICATION_ID + ".fileprovider",
it
)
takePictureRegistration.launch(imageUri)
}
}
private val takePictureRegistration =
registerForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess ->
if (isSuccess) {
mBinding.photoPreview.setImageURI(imageUri)
}
}
/**
* Function for onClick from XML
*
* Launches permission request for camera
*/
fun onRequestCameraClick(view: View? = null, callback: Runnable? = null) {
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
// update image
mBinding.iconCameraPermission.isEnabled = isGranted
val message = if (isGranted) {
"Camera permission has been granted!"
} else {
"Camera permission denied! :("
}
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
if (isGranted) {
callback?.run()
}
}.launch(Manifest.permission.CAMERA)
}
For full transparency the ImageUtils utility class has the createImageFile() method defined as follows and returns a File? when given context. Note that I am using the external files directory as the storage directory for my FileProvider.
object ImageUtils {
lateinit var currentPhotoPath: String
#Throws(IOException::class)
fun createImageFile(context: Context): File? {
// Create an image file name
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = absolutePath
}
}
}
Don't forget to add the uses-permission, uses-feature and provider tags to the AndroidManifest.
Also make sure the authorities attribute provided to the <provider> matches the 2nd String parameter passed to FileProvider.getUriForFile(context, authority, file) method. In my example, I have made my authority the package name + ".fileprovider". Read more about FileProvider from Google's documentation.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.captech.android_activity_results">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<application
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.captech.android_activity_results.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
</application>
</manifest>
My res/xml/file_paths is shown below. Because I am using getExternalFilesDir(), I am using the <external-files-path> tags in the XML.
Note: If you are NOT using the external files directory, you may want to look up which FileProvider storage directory you want to specify in your XML tags here.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="my_images"
path="/" />
</paths>
The result would display the imageUri in the ImageView:
I've been struggling with this same issue and only just came up with a (somewhat) tenable solution involving ContentResolver.
The documentation leaves a lot to the imagination. A major concern with this approach is that the captured image URI has to be managed external to the ActivityResultContract, which seems counterintuitive as the original question already points out.
I do not know of another way to insert media into the gallery that would solve that part of the problem, but I would absolutely love to see that solution.
// Placeholder Uri
var uri: Uri? = null
// ActivityResultContract for capturing an image
val takePicture =
registerForActivityResult(contract =
ActivityResultContracts.TakePicture()) { imageCaptured ->
if (imageCaptured) {
// Do stuff with your Uri here
}
}
...
fun myClickHandler() {
// Create a name for your image file
val filename = "${getTimeStamp("yyyyMMdd_HHmmss")}-$label.jpg"
// Get the correct media Uri for your Android build version
val imageUri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val imageDetails = ContentValues().apply {
put(MediaStore.Audio.Media.DISPLAY_NAME, filename)
}
// Try to create a Uri for your image file
activity.contentResolver.insert(imageUri, imageDetails).let {
// Save the generated Uri using our placeholder from before
uri = it
// Capture your image
takePicture.launch(uri)
} ?: run {
// Something went wrong
}
}

Android Share Intent for a Bitmap - is it possible not to save it prior sharing?

I try to export a bitmap from my app using share intent without saving a file for a temporal location. All the examples I found are two-step
1) save to SD Card and create Uri for that file
2) start the intent with this Uri
Is it possible to make it without requiring WRITE_EXTERNAL_STORAGE permission, saving the file [and removing it afterwards]? How to address devices without ExternalStorage?
I had this same problem. I didn't want to have to ask for the read and write external storage permissions. Also, sometimes there are problems when phones don't have SD cards or the cards get unmounted.
The following method uses a ContentProvider called FileProvider. Technically, you are still saving the bitmap (in internal storage) prior to sharing, but you don't need to request any permissions. Also, every time you share the bitmap the image file gets overwritten. And since it is in the internal cache, it will be deleted when the user uninstalls the app. So in my opinion, it is just as good as not saving the image. This method is also more secure than saving it to external storage.
The documentation is pretty good (see the Further Reading below), but some parts are a little tricky. Here is a summary that worked for me.
Set up the FileProvider in the Manifest
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
</application>
</manifest>
Replace com.example.myapp with your app package name.
Create res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
This tells the FileProvider where to get the files to share (using the cache directory in this case).
Save the image to internal storage
// save bitmap to cache directory
try {
File cachePath = new File(context.getCacheDir(), "images");
cachePath.mkdirs(); // don't forget to make the directory
FileOutputStream stream = new FileOutputStream(cachePath + "/image.png"); // overwrites this image every time
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
Share the image
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
Further reading
FileProvider
Storage Options - Internal Storage
Sharing Files
Saving Files
I try to export a bitmap from my app using share intent without saving a file for a temporal location.
In theory, this is possible. In practice, it is probably not possible.
In theory, all you need to share is a Uri that will resolve to the bitmap. The simplest approach is if that is a file that is directly accessible by the other application, such as on external storage.
To not write it to flash at all, you would need to implement your own ContentProvider, figure out how to implement openFile() to return your in-memory bitmap, and then pass a Uri representing that bitmap in the ACTION_SEND Intent. Since openFile() needs to return a ParcelFileDescriptor, I don't know how you would do that without an on-disk representation, but I have not spent much time searching.
Is it possible to make it without requiring WRITE_EXTERNAL_STORAGE permission, saving the file [and removing it afterwards]?
If you simply do not want it on external storage, you can go the ContentProvider route, using a file on internal storage. This sample project demonstrates a ContentProvider that serves up a PDF file via ACTION_VIEW to a PDF viewer on a device; the same approach could be used for ACTION_SEND.
If anyone still looking for easy and short solution without any storage permission (Supports nougat 7.0 as well). Here it is.
Add this in Manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
Now create provider_paths.xml
<paths>
<external-path name="external_files" path="."/>
</paths>
Finally Add this method to your activity/fragment (rootView is the view you want share)
private void ShareIt(View rootView){
if (rootView != null && context != null && !context.isFinishing()) {
rootView.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(rootView.getDrawingCache());
if (bitmap != null ) {
//Save the image inside the APPLICTION folder
File mediaStorageDir = new File(AppContext.getInstance().getExternalCacheDir() + "Image.png");
try {
FileOutputStream outputStream = new FileOutputStream(String.valueOf(mediaStorageDir));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (ObjectUtils.isNotNull(mediaStorageDir)) {
Uri imageUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", mediaStorageDir);
if (ObjectUtils.isNotNull(imageUri)) {
Intent waIntent = new Intent(Intent.ACTION_SEND);
waIntent.setType("image/*");
waIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
startActivity(Intent.createChooser(waIntent, "Share with"));
}
}
}
}
}
Update:
As #Kathir mentioned in comments,
DrawingCache is deprecated from API 28+. Use below code to use Canvas instead.
Bitmap bitmap = Bitmap.createBitmap(rootView.getWidth(), rootView.getHeight(), quality);
Canvas canvas = new Canvas(bitmap);
Drawable backgroundDrawable = view.getBackground();
if (backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
} else {
canvas.drawColor(Color.WHITE);
}
view.draw(canvas);
return bitmap;
This for sharing CardView as an Image then saving it in the cache subdirectory of the app's internal storage area.
hope it will be helpful.
#Override
public void onClick(View view) {
CardView.setDrawingCacheEnabled(true);
CardView.buildDrawingCache();
Bitmap bitmap = CardView.getDrawingCache();
try{
File file = new File(getContext().getCacheDir()+"/Image.png");
bitmap.compress(Bitmap.CompressFormat.PNG,100,new FileOutputStream(file));
Uri uri = FileProvider.getUriForFile(getContext(),"com.mydomain.app", file);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("image/jpeg");
getContext().startActivity(Intent.createChooser(shareIntent, "Share"));
}catch (FileNotFoundException e) {e.printStackTrace();}
}
});
Here is working method to make a screenshot of own app and share it as image via any messanger or email client.
To fix the bitmap not updating problem I improved Suragch's answer, using Gurupad Mamadapur's comment and added own modifications.
Here is code in Kotlin language:
private lateinit var myRootView:View // root view of activity
#SuppressLint("SimpleDateFormat")
private fun shareScreenshot() {
// We need date and time to be added to image name to make it unique every time, otherwise bitmap will not update
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss")
val currentDateandTime = sdf.format(Date())
val imageName = "/image_$currentDateandTime.jpg"
// CREATE
myRootView = window.decorView.rootView
myRootView.isDrawingCacheEnabled = true
myRootView.buildDrawingCache(true) // maybe You dont need this
val bitmap = Bitmap.createBitmap(myRootView.drawingCache)
myRootView.isDrawingCacheEnabled = false
// SAVE
try {
File(this.cacheDir, "images").deleteRecursively() // delete old images
val cachePath = File(this.cacheDir, "images")
cachePath.mkdirs() // don't forget to make the directory
val stream = FileOutputStream("$cachePath$imageName")
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) // can be png and any quality level
stream.close()
} catch (ex: Exception) {
Toast.makeText(this, ex.javaClass.canonicalName, Toast.LENGTH_LONG).show() // You can replace this with Log.e(...)
}
// SHARE
val imagePath = File(this.cacheDir, "images")
val newFile = File(imagePath, imageName)
val contentUri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", newFile)
if (contentUri != null) {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // temp permission for receiving app to read this file
shareIntent.type = "image/jpeg" // just assign type. we don't need to set data, otherwise intent will not work properly
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(shareIntent, "Choose app"))
}
}

Categories

Resources