for my app i'm attempting to create a csv file and share it via email/google drive.
however when i go to share the csv file, im am presented with the following "toast" . The code is located within a fragment.
upload was unsuccessful request contained no data
the following is my current code:
val HEADER = "ID, Pa, m/s, Actual L/s, Design Pa, Design L/s, Design %"
var fileName = "file://" + Environment.getExternalStorageDirectory() + "/Folder" + "/" + "mycsv.csv"
println(" Debug: pressed successfully!")
var path = activity!!.getExternalFilesDir(null) //get file directory for this package
//(Android/data/.../files | ... is your app package)
println(" Debug: path successfully!")
//create fileOut object
var fileOut = File(path, "mycsv.csv")
println(" Debug: file successfully!")
//delete any file object with path and filename that already exists
fileOut.delete()
println(" Debug: deleted successfully!")
//create a new file
fileOut.createNewFile()
println(" Debug: file created successfully!")
//append the header and a newline
fileOut.writeText(HEADER)
fileOut.writeText("\n")
// trying to append some data into csv file
println(" Debug: csv written successfully!")
println("Debug:$fileOut")
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_STREAM, fileName)
sendIntent.type = "text/csv"
startActivity(Intent.createChooser(sendIntent, "Share File"))
println(" Debug: sent page open successfully!")
i have tried following similar questions asked on this site, however none of them were able to help as they were not in kotlin or i was unable to interpret it for my example. The attempt i have in this code is the closest i have gotten.
i have also tried:
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(FileName))
as it was a suggested answer for Kotlin Android create and share CSV file
Would Anyone have any ideas on how to fix this?
i got it to work by first adding the following code into the androidManifest.xml
with in the section.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.YOUR_PACKAGE_NAME.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
after this create an xml file in res>xml>provider_paths*
inside the provider_paths.xml aded the following code
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="."/>
<external-path
name="external_files"
path="."/>
</paths>
finally to create the CSV file the code below was used inside the onClickListener.
val csv_header = "ID, Pa, m/s, Actual L/s, DesignPa, Design L/s, Design %"
var filename = "export.csv"
var path = context!!.filesDir.absolutePath//get file directory for this package
//(Android/data/.../files | ... is your app package)
//create fileOut object
var fileOut = File(path, filename)
//delete any file object with path and filename that already exists
fileOut.delete()
//create a new file
fileOut.createNewFile()
//append the header and a newline
fileOut.appendText(csv_header)
fileOut.appendText("\n")
// trying to append some data into csv file
fileOut.appendText("$123")
fileOut.appendText(",")
fileOut.appendText("456")
println("debug: Write CSV successfully!")
if(fileOut.exists())
try {
val uri = FileProvider.getUriForFile(context!!, BuildConfig.APPLICATION_ID + ".provider", fileOut)
val intent = Intent(Intent.ACTION_SEND)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.type = "text/csv"
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
startActivity(Intent.createChooser(intent, "Share File"))
println(" Debug1: sent page open successfully!")
}catch (e: java.lang.Exception) {
e.printStackTrace()
println("debug: failed")
}
Related
I have an export button in my app that executes the below code. The email app opens correctly with the subject etc., but it's missing the attachment. I don't seem to be able to get the attachment to work. Any ideas what I'm doing wrong?
val filename = "Export.csv"
val path = context?.getExternalFilesDir(null) //get file directory for this package
//create fileOut object
val fileOut = File(path, filename)
//delete any file object with path and filename that already exists
fileOut.delete()
//create a new file
fileOut.createNewFile()
//append the header and a newline
fileOut.appendText("Case Number,Form Type,Status,Last Updated Date")
fileOut.appendText("\n")
for (item in arrayAll) {
fileOut.appendText("${item.number},${item.formType},${item.status},${item.LUD}")
fileOut.appendText("\n")
}
val contentUri = FileProvider.getUriForFile(this.requireContext(),"${BuildConfig.APPLICATION_ID}.fileProvider", fileOut)
val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:") // only email apps should handle this
putExtra(Intent.EXTRA_SUBJECT, "Export.csv")
putExtra(Intent.EXTRA_STREAM, contentUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
this.startActivity(emailIntent)
Manifest:
<provider
android:authorities="${applicationId}.fileProvider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
provider_paths:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path="." />
</paths>
Following from you latest comment; According to the Android docs Intent.ACTION_SENDTO is for when you don't want attachments, I think you need to change this line val emailIntent = Intent(Intent.ACTION_SENDTO).apply { to
val emailIntent = Intent(Intent.ACTION_SEND).apply { to get your email app to display the attachment
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"
}
I am creating an app which generates a csv file, and then sends it via email.
It does work if I choose Whatsapp, or Google Drive, but if I select Gmail, I get a toast "Unable to attach file".
In the Logcat, I get the following lines:
2020-05-16 16:03:43.518 30779-30779/? W/Gmail: Gmail:No collectionId found for event forward
2020-05-16 16:03:43.518 30779-30779/? W/Gmail: Gmail:No itemId found for event forward
So I suppose it is a problem with my path, but I cannot manage to figure out what is the issue.
See the code below:
String today = new SimpleDateFormat("yyyyMMdd_HHmm").format(Calendar.getInstance().getTime());
//saving the file into device
FileOutputStream out = openFileOutput("Data_"+today+".csv", Context.MODE_PRIVATE);
out.write(data.toString().getBytes());
out.close();
//exporting
Context context = getApplicationContext();
File filelocation = new File(getFilesDir(), "Data_"+today+".csv");
Uri path = FileProvider.getUriForFile(context, "com.aaaa.bbbb.fileprovider", filelocation);
Intent fileIntent = new Intent(Intent.ACTION_SEND);
fileIntent.setType("text/csv");
String to[] = {"aaa#bbb.com"};
fileIntent.putExtra(Intent.EXTRA_EMAIL,to);
fileIntent.putExtra(Intent.EXTRA_SUBJECT, "Data_"+today+".csv");
fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
fileIntent.putExtra(Intent.EXTRA_STREAM, path);
Log.d(LOG_TAG,"filelocation = " + filelocation.toString());
Log.d(LOG_TAG,"Uri = " + path.toString());
startActivity(Intent.createChooser(fileIntent, "Send email"));
The file provider xml:
<paths>
<files-path
name="data"
path="."/>
</paths>
Here is the provider definition from the manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.aaaa.bbbb.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_path" />
</provider>
The log.d provides the following:
2020-05-16 16:16:40.745 2030-2030/com.aaaa.bbbb D/Export: filelocation = /data/user/0/com.aaaa.bbbb/files/Data_20200516_1616.csv
2020-05-16 16:16:40.745 2030-2030/com.aaaa.bbbb D/Export: Uri = content://com.aaaa.bbbb.fileprovider/data/Data_20200516_1616.csv
Any idea of what is wrong?
I have an option in my app that share image to whatsapp,facebook etc. For share image through intent, i want specific image from image view on which share button is clicked.
I have the following code that does not work.it share an empty file to whatsapp.
val shareBtn = findViewById<TextView>(R.id.share_btn)
val postImage = findViewById<ImageView>(R.id.post_image)
val path:String?=postImage.tag.toString()
val file= File(path)
shareBtn.setOnClickListener {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image"
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(Intent.createChooser(intent, "Share Image"))
First to store image of ImageView , you need to convert to Bitmap
val bitMap : Bitmap =imageview.getDrawingCache();
now store this image to file
val bos : ByteArrayOutputStream = ByteArrayOutputStream();
bitMap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
val file : File = File(Environment.getExternalStorageDirectory() + File.separator + "your_file.jpg");
try {
file.createNewFile();
val fos : FileOutputStream = FileOutputStream(file);
fos.write(bos.toByteArray());
} catch (IOException e) {
e.printStackTrace();
}
now create an intent by specifying type 'image/jpeg'
and setting extra stream and path of the file that is to be shared
val intent= new Intent(Intent.ACTION_SEND);
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/your_file.jpg"));
And start Activity by creating chooser
startActivity(Intent.createChooser(intent, "Share Image"));
a great way for share image file ( Kotlin ) :
first create a folder named xml in the res folder and create a new XML Resource File named provider_paths.xml and put the below code inside it :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="."/>
<external-path
name="external_files"
path="."/>
</paths>
now go to the manifests folder and open the AndroidManifest.xml and then put the below code inside the <application> tag :
<provider
android:name="androidx.core.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_paths.xml file path in this example
</provider>
now you put the below code in the setOnLongClickListener :
button.setOnLongClickListener {
try {
val file = File("pathOfFile")
if(file.exists()) {
val uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file)
val intent = Intent(Intent.ACTION_SEND)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setType("image/*")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
toast("Error")
}
}
So here's the Scenario:
I am downloading a PDF from network and saving it in /0/Download/NSIT - Notices/"fileName".pdf
Now sending Intent like this:
private void openPDF(String fileName) {
Log.d(TAG, "openPDF: called");
Intent intent = new Intent(Intent.ACTION_VIEW);File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download", "NSIT - Notices");
File myPDF = new File(Environment.getExternalStorageDirectory() + "/Download/NSIT - Notices", fileName + ".pdf");
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", myPDF);
Log.d(TAG, "openPDF: intent with uri: " + uri);
intent.setDataAndType(uri, "application/pdf");
context.startActivity(Intent.createChooser(intent, "Open with..."));
}
Now any PDF reader (Google Drive PDF Reader, System PDF Viewer) is saying
The File Does Not Exist
Image:
The Google one is sitting not doing anything (that's why not included its image)
Mainfest.xml
<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>
Provider_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
I am creating File here in AsyncTask but opening from the postExecute of that AsyncTask
File myDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download", "NSIT - Notices");
File myPDF = new File(myDir, params[1] + ".pdf");
if (!myDir.exists())
myDir.mkdirs();
try {
fileOutputStream = new FileOutputStream(myPDF);
fileOutputStream.write(response.bodyAsBytes());
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "doInBackground: Download complete");
Logcat
Previously it was throwing FileUriExposedException then I fixed with this FileProvider thing. The problem is the pdf file downloaded by my app is created successfully and I am able to open it successfully from any File Manager (ES File Explorer now) but not from my app :(. I am new to FileProvider. Need help!
PS: That's not my device problem either. Tested with two other phones which has different ROMs and API >= 23
Please set intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
It will grant access of your file to other providers
See documentation for File provider
Whether the content provider is available for other applications to use:
true: The provider is available to other applications. Any application
can use the provider's content URI to access it, subject to the
permissions specified for the provider.
false: The provider is not available to other applications.
access to the provider to your applications. Only applications that
have the same user ID (UID) as the provider will have access to it.
Try to change android:exported="true"