I have a function that can write a file in Android file system, but I want to append data.
My function create a new file every time I call it.
How can I modify to have the data appended to the file?
Thank you
fun saveDocument(context : Context, fileName: String, text : String): Boolean {
try {
val collection = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(Environment.DIRECTORY_DOCUMENTS, context.getString(R.string.app_name)) // subfolder with name of the App
//val date = System.currentTimeMillis() // current datetime
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
//put(MediaStore.MediaColumns.MIME_TYPE, "application/txt")
//put(MediaStore.MediaColumns.DATE_ADDED, date)
//put(MediaStore.MediaColumns.DATE_MODIFIED, date)
put(MediaStore.MediaColumns.RELATIVE_PATH, "$dirDest${File.separator}")
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
val imageUri = context.contentResolver.insert(collection, contentValues)
imageUri?.let { uri ->
//context.contentResolver.openOutputStream(uri, "w").use { out -> out?.write(text.toByteArray()) } // write
context.contentResolver.openOutputStream(uri, "wa").use { out -> out?.write(text.toByteArray()) } // append
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
return(true)
}
catch (e: FileNotFoundException) {
return(false)
}
}
Related
How do I get original size image after capture in Kotlin so that I can send the original size photo to a server? Here is my code:
DeliveryPresenter:
import com.stevenmorris.delivery.util.ImageStorageUtils
override fun capturePhoto() {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
activity.startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
override fun onCapturePhotoResults(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
val imageBitmap = data?.extras?.get("data") as Bitmap
view.onPhotoCaptured(imageBitmap);
}
}
DeliveryActivity:
private lateinit var imgPhoto: AppCompatImageView
private var photo: Bitmap? = null;
override fun onPhotoCaptured(photo: Bitmap) {
this.photo = photo
imgPhoto.setImageBitmap(photo)
}
ImageStorageUtils.kt
object ImageStorageUtils {
val TAG = javaClass.simpleName
fun savePhoto(activity: Activity,photo: Bitmap, fileName: String): Boolean {
val root = activity.filesDir.absolutePath;
val myDir = File("$root/photos")
myDir.mkdirs()
val file = File(myDir, fileName)
if (file.exists()) file.delete()
try {
val out = FileOutputStream(file)
photo.compress(Bitmap.CompressFormat.JPEG, 100, out)
out.flush()
out.close()
return true
} catch (e: Exception) {
AppLogger.e(TAG, e.message, e)
return false
}
}
}
Initial thoughts were can I save the photo without compressing it? or is there a way to save the photo in its original size directly?
I wrote an App, in Kotlin with Android Studio that write some strings to a file.
All work, I can write and read inside the App, but I can't see the file looking in Documents folder.
How can I use the folder Documents as a storage space?
Thank you
These are the function I use:
fun saveTextFile(view: View, nomeFile: String, testo: String, contesto: Context){
var fileStream: FileOutputStream
try {
fileStream = contesto.openFileOutput(nomeFile, MODE_APPEND) // OK esegue append
fileStream.write(testo.toByteArray())
fileStream.close()
}catch (e: Exception){
e.printStackTrace()
}
}
fun readTextFile(view: View, nomeFile: String, contesto: Context): String{
var fileInputStream: FileInputStream? = null
fileInputStream = contesto.openFileInput(nomeFile)
var inputStreamReader: InputStreamReader = InputStreamReader(fileInputStream)
val bufferedReader: BufferedReader = BufferedReader(inputStreamReader)
val stringBuilder: StringBuilder = StringBuilder()
var text: String? = null
while ({ text = bufferedReader.readLine(); text }() != null) {
stringBuilder.append(text)
}
inputStreamReader.close();
return(stringBuilder.toString())
}
Thank you, Livio
For writing in Documents folder of your device , you just need to make use of MediaStore for the same. You can take input for this function anything that you want like String , bitmap , PdfDocument and other's too .
For Your UseCase you can do the following ,
Global Variable :
private var imageUri: Uri? = null
override suspend fun saveDocument(context : Context, text : String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val date = System.currentTimeMillis()
val fileName = "$date.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}")
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
}
val imageUri = context.contentResolver.insert(collection, contentValues)
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "w").use { out -> out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
For Updating the already existing file , do the following . After creating file for the first time I have saved the imageUri in a global variable (If you want to store it permanently / or for a while you can use Jetpack Datastore / Shared Preference to save the same ):
suspend fun updateData(context: Context,text : String){
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "wa").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
For Reading the File , Do the following :
suspend fun read(context: Context, source: Uri): String = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openInputStream(source)?.use { stream -> stream.readText() }
?: throw IllegalStateException("could not open $source")
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
This is how the final code looks like :
class MainActivity : AppCompatActivity() {
private lateinit var btn: Button
private var imageUri: Uri? = null
private lateinit var btn2: Button
private lateinit var btn3 : Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn = findViewById(R.id.btnAdd)
btn2 = findViewById(R.id.getText)
btn3 = findViewById(R.id.updateText)
btn.setOnClickListener {
lifecycleScope.launch {
saveDocument(applicationContext, "Original ")
}
}
btn3.setOnClickListener {
lifecycleScope.launch {
updateData(applicationContext,"Appended")
}
}
btn2.setOnClickListener {
lifecycleScope.launch {
imageUri?.let { it1 ->
val data = read(applicationContext, it1)
Toast.makeText(applicationContext, "The data is $data ", Toast.LENGTH_LONG)
.show()
}
}
}
}
suspend fun saveDocument(context: Context, text: String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val date = System.currentTimeMillis()
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
imageUri = context.contentResolver.insert(collection, contentValues)
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "w").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
suspend fun updateData(context: Context, text: String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "wa").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
suspend fun read(context: Context, source: Uri): String = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openInputStream(source)?.use { stream -> stream.readText() }
?: throw IllegalStateException("could not open $source")
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
I have three buttons . With the first I create a file , then the uri gets stored in the global variable . Then onClick of second button I add to the already existing file and then read the file using the third button using the same imageUri stored in the global variable
This is the demo for the same . Check when the buttons are being pressed and the output in the form of Toast at the bottom .
I am very new to Android Kotlin programming. I am making an app to create, read and delete file using Storage Access Framework. I did it by implementing deprecated startActivityForResult intent to perform file read, create, delete tasks. I tried to implement it using Activity Result API but I got stuck in request code which I could not find it.
In new implementation, there is resultCode but no requestCode.
My implemetation is below. How I can I implement the following using Activity Result API ?
How can I get request code from Activity Result API ?
fun createFile()
{
val filename: String = etFileName.text.toString()
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = "text/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_TITLE, filename)
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
}
fun readFile(){
etFileName.setText("")
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/*"
}
startActivityForResult(intent, READ_REQUEST_CODE)
}
fun deleteFile(uri: Uri){
val filename = queryName(contentResolver, uri)
DocumentsContract.deleteDocument(contentResolver, uri)
Toast.makeText(applicationContext, "Done deleting $filename", Toast.LENGTH_SHORT).show()
}
fun writeText(uri: Uri) : String{
//var writetext: String = "typing high speed"
var writeStream = contentResolver.openFileDescriptor(uri, "w")
val filename: String = queryName(contentResolver, uri)
etFileName.setText(filename)
var fileOutputStream = FileOutputStream(writeStream?.fileDescriptor)
fileOutputStream.write(filename.toByteArray())
fileOutputStream.close()
return filename.toString()
}
fun openFileContent(uri: Uri): String{
val inputStream = contentResolver.openInputStream(uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
val filename = queryName(contentResolver, uri)
etFileName.setText(filename)
var currentline = reader.readLine()
while (currentline != null) {
stringBuilder.append(currentline + "\n")
currentline = reader.readLine()
}
inputStream?.close()
val str = stringBuilder.toString()
Log.d("Uri Read", ": $str")
return str
}
private fun queryName(resolver: ContentResolver, uri: Uri): String {
val returnCursor: Cursor = resolver.query(uri, null, null, null, null)!!
val nameIndex: Int = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
val name: String = returnCursor.getString(nameIndex)
returnCursor.close()
return name
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Read", "$uri")
if (uri != null) {
val readfile: String = openFileContent(uri)
etContent.setText(readfile)
}
} else if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
Log.i("Uri Write", "Uri: $uri") // 1
val write = writeText(uri) // 2
etContent.setText(write)
}
} else if (requestCode == DELETE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Delete", "$uri")
if (uri != null) {
deleteFile(uri)
}
}
}
Since the above method is deprecated, I tried to implement it using Intent launcher, but there is no reference to requestCode anywhere so I am stuck here. It is as shown below :
val startforFile: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result: ActivityResult ->
// could not find reference to requestCode here
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Read", "$uri")
if (uri != null) {
val readfile: String = openFileContent(uri)
etContent.setText(readfile)
}
} else if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
Log.i("Uri Write", "Uri: $uri") // 1
val write = writeText(uri) // 2
etContent.setText(write)
}
} else if (requestCode == DELETE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Delete", "$uri")
if (uri != null) {
deleteFile(uri)
}
}
}
Can anyone show me how it is done correctly ?
There are two ways with ActivityResultContracts.StartActivityForResult.
Make multiple requests by registering multiple listeners:
val startForFileRead: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result: ActivityResult ->
if (resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Read", "$uri")
if (uri != null) {
val readfile: String = openFileContent(uri)
etContent.setText(readfile)
}
}
}
val startForFileWrite: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result: ActivityResult ->
if (resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
Log.i("Uri Write", "Uri: $uri") // 1
val write = writeText(uri) // 2
etContent.setText(write)
}
}
}
val startForFileDelete: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result: ActivityResult ->
if (resultCode == Activity.RESULT_OK) {
val uri =resultData?.data
Log.d("Uri Delete", "$uri")
if (uri != null) {
deleteFile(uri)
}
}
}
Put an extra in your Intent returned from the other activity. Unpack it as your request code.
const val REQUEST_CODE = "REQUEST_CODE"
//...
val startForFile: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val requestCode = result.data?.extras?.getInt(REQUEST_CODE)
if (requestCode == null) {
Log.e("FileResultLauncher", "No REQUEST_CODE was returned in data intent.")
return#registerForActivityResult
}
val uri = result.data?.data
if (uri == null || result.resultCode != RESULT_OK) {
Log.i("FileResultLauncher", "No Uri returned or result wasn't OK.")
return#registerForActivityResult
}
when (requestCode) {
READ_REQUEST_CODE -> {
Log.d("Uri Read", "$uri")
val readfile: String = openFileContent(uri)
etContent.setText(readfile)
}
WRITE_REQUEST_CODE -> {
Log.i("Uri Write", "Uri: $uri") // 1
val write = writeText(uri) // 2
etContent.setText(write)
}
DELETE_REQUEST_CODE -> {
Log.d("Uri Delete", "$uri")
deleteFile(uri)
}
}
}
I just want to write a struct type array into a file, but after I write it, the file is not created without any error message!!!???
The code :
struct temp {
var a : String = ""
var b : Date = Date()
init(
a : String = "",
b : Date = Date(),
) {
self.a = ""
self.b = Date()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var b = [temp]()
var c = temp()
c.a = "John"
c.b = Date()
b.append(c)
c.a = "Sally"
b.append(c)
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent("testFile")
do{
(b as NSArray).write(to: fileURL, atomically: true)
}catch{
print(error)
}
}
getTheFile()
}
func getTheFile() {
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent("testFile")
do {
print(try String(contentsOf: fileURL))
}catch{
print("read error:")
print(error)
}
}
}
It has a error message in getTheFile()
read error:
Error Domain=NSCocoaErrorDomain Code=260 "The file “ testFile” couldn’t be opened because there is no such file.
You cannot write custom structs to disk. You have to serialize them.
The easist way is the Codable protocol and PropertyListEncoder/Decoder
struct Temp : Codable { // the init method is redundant
var a = ""
var b = Date()
}
var documentsDirectory : URL {
return try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var b = [Temp]()
var c = Temp()
c.a = "John"
c.b = Date()
b.append(c)
c.a = "Sally"
b.append(c)
let fileURL = documentsDirectory.appendingPathComponent("testFile.plist")
do {
let data = try PropertyListEncoder().encode(b)
try data.write(to: fileURL)
getTheFile()
} catch {
print(error)
}
}
func getTheFile() {
let fileURL = documentsDirectory.appendingPathComponent("testFile.plist")
do {
let data = try Data(contentsOf: fileURL)
let temp = try PropertyListDecoder().decode([Temp].self, from: data)
print(temp)
} catch {
print("read error:", error)
}
}
Note:
In Swift never use the NSArray/NSDictionary APIs to read and write property lists. Use either Codable or PropertyListSerialization.
If the array's contents are all property list objects (like: NSString, NSData, NSArray, NSDictionary) then only you can use writeToFile method to write the array in a file.
Here in your implementation, the array contains structure which is a value type, not an object. This is why you are getting an error.
I have the below module which is part of a larger program for searching the text within DOCX files. This is the code for when the search button is clicked;
{
this.resultListView.Items.Clear();
try
{
foreach (var filePath in Search(this.txtDirectory.Text, this.txtSearch.Text, this.cBoxUseSubdirectories.Checked, this.cBoxCaseSensitive.Checked, this.rBtnRegex.Checked))
{
var file = new FileInfo(filePath);
this.resultListView.Items.Add(new ListViewItem(new string[] { file.Name, string.Format("{0:0.0}", file.Length / 1024d), file.FullName}));
}
}
catch (Exception ex)
{
MessageBox.Show(this, string.Format("Exception details:\n{0}", ex), string.Format("Exception '{0}' occurred.", ex.GetType()), MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
This is the code for the Search method which is called with the arguments above;
private static IEnumerable<string> Search(string directory, string searchString, bool searchSubdirectories, bool caseSensitive, bool useRegex)
{
var isMatch = useRegex ? new Predicate<string>(x => Regex.IsMatch(x, searchString, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase))
: new Predicate<string>(x => x.IndexOf(searchString, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0);
foreach (var filePath in Directory.GetFiles(directory, "*.docx", searchSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
{
string docxText;
using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
docxText = new DocxToStringConverter(stream).Convert();
if (isMatch(docxText))
yield return filePath;
}
}
There are other classes which I do not think are relevant here.
the results are populated in a listview via this;
this.resultListView.Items.Add(new ListViewItem(new string[] { file.Name, string.Format("{0:0.0}", file.Length / 1024d), file.FullName}));
}
I want to add an extra column to show the file creation date for the found files. I tried adding this;
var fileCreatedDate = File.GetCreationTime(filePath);
then adding file.fileCreatedDate to the this.resultListView.Items.Add but it didn't work.
Any suggestions on how to pull the file created date out of the existing code?
Return an IEnumerable of Tuple instances.
private static IEnumerable<Tuple<string, DateTime>> Search(string directory, string searchString, bool searchSubdirectories, bool caseSensitive, bool useRegex)
{
var isMatch = useRegex ? new Predicate<string>(x => Regex.IsMatch(x, searchString, caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase))
: new Predicate<string>(x => x.IndexOf(searchString, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0);
foreach (var filePath in Directory.GetFiles(directory, "*.docx", searchSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
{
string docxText;
using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
docxText = new DocxToStringConverter(stream).Convert();
if (isMatch(docxText))
yield return new Tuple<string, DateTime>(filePath, System.IO.File.GetLastWriteTime(filePath));
}
}
Tuple will have two properties: Item1, and Item2. Item1 is the path, and Item2 is the date.
You could also use your own class in place of the Tuple, but the Tuple was simple to code.
Through some trial and error I managed to sort the problem.
I added this to the initial button click code;
var fileCreatedDate = File.GetCreationTime(filePath);
and then this;
this.resultListView.Items.Add(new ListViewItem(new string[] { file.Name, string.Format("{0:0.0}", file.Length / 1024d), file.FullName, fileCreatedDate.ToString()}));
the fileCreated.ToString() sorts it.