How to read json file from assests in android using Kotlin? - android

I have already done with java but find difficult with Kotlin.
I have already search with google but none of them work for me.
/**
* Get the json data from json file.
*
* #param context the context to acces the resources.
* #param fileName the name of the json file
* #return json as string
*/
public static String getJsonFromAsset(Context context, String fileName) {
String json = "";
try {
InputStream stream = context.getAssets().open(fileName);
int size = stream.available();
byte[] buffer = new byte[size];
stream.read(buffer);
stream.close();
json = new String(buffer, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return json;
}
I want this code in Kotlin.

Reading json file from assets folder in Kotlin is very easy, just use the following code
val fileInString: String =
applicationContext.assets.open(fileName).bufferedReader().use { it.readText() }

Java codes can be converted to Kotlin from Android Studio too.
Here is the converted solution with the extension function of Context.
#Throws(IOException::class)
fun Context.readJsonAsset(fileName: String): String {
val inputStream = assets.open(fileName)
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
inputStream.close()
return String(buffer, Charsets.UTF_8)
}

You can use the following
class LocalJSONParser {
companion object {
fun inputStreamToString(inputStream: InputStream): String {
try {
val bytes = ByteArray(inputStream.available())
inputStream.read(bytes, 0, bytes.size)
return String(bytes)
} catch (e: IOException) {
return ""
}
}
}
}
// jsonFileName = "data.json"
inline fun <reified T> Context.getObjectFromJson(jsonFileName: String): T {
val myJson =LocalJSONParser.inputStreamToString(this.assets.open(jsonFileName))
return Gson().fromJson(myJson, T::class.java
}

Related

How to read data from JSON file on the resources directory?

How to read data from JSON file on the resources directory?
I need to read a json file on the resources directory, convert it to a data class ("User")
I'm trying to adapt the following code
private fun getJSONFromAssets(): String? {
var json: String? = null
val charset: Charset = Charsets.UTF_8
try {
val myUsersJsonFile = assets.open("users.json")
val size = myUsersJsonFile.available()
val buffer = ByteArray(size)
myUsersJsonFile.read(buffer)
myUsersJsonFile.close()
json = String(buffer, charset)
} catch (ex: IOException) {
ex.printStackTrace()
return null
}
return json
}
but assets.open("users.json") is not recognized.
How is the best approach to read JSON files on the resources directory (mock data)?
You just need a minor change in your function...
private fun getJSONFromAssets(context: Context): String? {
...
val myUsersJsonFile = context.assets.open("users.json")
...
}
Assuming that your json file is at src/main/assets.
If you need to read a JSON file from the src/main/res/raw folder. You can use:
private fun getJSONFromAssets(context: Context): String? {
...
val myUsersJsonFile = context.resources.openRawResource(R.raw.users)
...
}
As you can see, you need a Context, so you can call from your activity.
getJSONFromAssets(this) // "this" is your activity (or another Context)

android, when read the file from res/raw, context.getAssets().open(fileName), the content read in are corrupted

on Android, using com.google.gson:gson:2.8.5,
when passing a josnString and when the json is large (noticed for example when string length is 669304), it got
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException:
Unterminated string at line 2902 column 564904 path $.items.result[10].quoted_status.full_text
or different errors:
the json has array of sub json strings, structured like,
{
"items": {
"result": [
{subJsonString...},
{subJsonString...},
...
]
}
}
and the json string is stored in the res/raw/jsonstring.txt, and read in at runtime before give to Gson fro deserialization.
if reduce the sub json elements in the array (i.e. 10 items or less in the array) it works fine, and the individual json elements string all working fine. But when the array has more items it starts to throw.
update: it seems is problem from reading the json string from res/raw
after further look,
the log shows the output json string read from the res/raw/jsonString.txt are corrupted, when there are more items. Here after adding the 11th sub item into the json string array, it shows 000000000... at certain point (the 11th item who encountered issue are just a copy from the 1st item, so the string should be fine)
here is the code how the raw string is read, how it logs it
val jsonStr = getJsonFromFile(context, "res/raw/jsoninraw.txt")
returnedModel = gson.fromJson<T>(jsonStr, dataClassType)
......
fun getJsonFromFile(context: Context?, fileName: String): String? {
val json: String
try {
val inputStream: InputStream = context.getAssets().open(fileName)
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.use { it.read(buffer) }
json = String(buffer)
} catch (ioException: IOException) {
ioException.printStackTrace()
return null
}
return json
.also {
logString(it)
}
}
fun logString(jsonStr: String) {
val chunkSize = 512
var i = 0
while (i < jsonStr.length) {
Log.e("+++", jsonStr.substring(i, Math.min(jsonStr.length, i + chunkSize)))
i += chunkSize
}
}
what are the better way to deserialize the json string into model?
Found the problem that the ByteArray has 64k limit, so anything beyond is corrupted.
the updated the getJsonFromFile() works.
fun getJsonFromFile(context: Context?, fileName: String?): String? {
val returnString = StringBuilder()
var inputStream: InputStream? = null
var isr: InputStreamReader? = null
var input: BufferedReader? = null
try {
inputStream = if (context == null) {
val cLoader = this.javaClass.classLoader
cLoader.getResourceAsStream(fileName)
} else {
context.getAssets().open(fileName)
}
// either #1:
// returnString.append(inputStream?.readBytes()?.toString(Charsets.UTF_8))
// or #2:
inputStream?.let {
val inputString = inputStream.bufferedReader().use { it.readText() }
returnString.append(inputString)
}
// or #3:
// isr = InputStreamReader(inputStream)
// input = BufferedReader(isr)
// var line: String? = ""
// while (input.readLine().also { line = it } != null) {
// returnString.append(line)
// }
} catch (e: Exception) {
e.printStackTrace()
return null
} finally {
try {
isr?.close()
inputStream?.close()
input?.close()
} catch (e2: Exception) {
e2.message
}
}
return returnString.toString()
}

Saving a two dimensional array (matrix) outside the life cycle of the application

For storing two-dimensional integer data (matrix) during the life of an application, it takes only one line of code (int myArray[][] = new int[][]) to store this data within the activity, and a relatively simple procedure (like for any other type of variable) using "intent" for transfer between activities.
However, if you want to store exactly the same data outside the runtime of the application, all the solutions that have been offered (for which I'm grateful) involve dozens of code lines.
Does anyone have a simple solution (I mean, as simple a the transfer between activities, or as simple as saving a string variable to SavedPreferences) for saving matrices outside application runtime?
You basically just need to convert the 2D array into a string and then store it in a file. Something along the following lines should work as you require.
private fun writeFileOnInternalStorage(context: Context, sFileName: String, sBody: Array<IntArray>) {
val file = File(context.getFilesDir(), "mydir")
if (!file.exists()) {
file.mkdir()
}
try {
val gpxfile = File(file, sFileName)
val writer = FileWriter(gpxfile)
writer.append(sBody.map {
//Convert the array to string using separators
it.joinToString(separator = ",")
}.joinToString(separator = "|"))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun readFileOnInternalStorage(context: Context, sFileName: String): Array<IntArray> {
var result = emptyArray<IntArray>()
val file = File(context.getFilesDir(), "mydir")
if (!file.exists()) {
return result
}
try {
val gpxfile = File(file, sFileName)
val reader = FileReader(gpxfile)
val content = reader.readText()
//Convert the string back into a 2D array using the separators
result = content.split("|").map {
it.split(",").map {
Integer.parseInt(it)
}.toIntArray()
}.toTypedArray()
reader.close()
} catch (e: Exception) {
e.printStackTrace()
}
return result
}

Encrypted File Transfer Could Not Be Recreated on Android

I am encrypting a file in Node.js client side and saving it on server as file, when I try to decrypt file it is not in the right format.
Encryptor.encryptFile = function (inputPath, outputPath, key, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
options = Encryptor.combineOptions(options);
var keyBuf = new Buffer(key);
console.log(key)
var inputStream = fs.createReadStream(inputPath);
var outputStream = fs.createWriteStream(outputPath);
var cipher = crypto.createCipher(options.algorithm, keyBuf);
inputStream.on('data', function (data) {
console.log(data.length)
var buf = new Buffer(cipher.update(data));
outputStream.write(buf);
});
inputStream.on('end', function () {
try {
var buf = new Buffer(cipher.final('binary'), 'binary');
outputStream.write(buf);
outputStream.end();
outputStream.on('close', function () {
return callback();
});
} catch (e) {
fs.unlink(outputPath);
return callback(e);
}
});
};
Above is the encryption code, and on Android side I use the following code.
FIle Downloading:
Ion.with(this).load(documentModel.filePath)
.progress { downloaded, total -> println("$downloaded / $total") }
.write(file)
.setCallback { e, result ->
if (result != null) {
// val keys = MedpayDatabaseHelper.getInstance(this#ViewMedicalRecordActivity)?.keyDao()?.getKeys()
val handler = EncryptionHandler()
val decrepted = handler.decryptFile(handler.getSymmetricKey(), result)
val outfile = File(dir, "output.pdf")
pdfView.fromBytes(decrepted).load()
} else {
Toast.makeText(this#ViewMedicalRecordActivity, "Failed to load file", Toast.LENGTH_LONG).show()
}
}
Following is the decryption code.
fun decryptFile(symmetricKey: String, file: File): ByteArray {
encryptionAlgorithm = "AES"
encryptionMode = "/ECB"
paddingMode = "/NoPadding"
return decrypt(symmetricKey, file)
}
private fun decrypt(decryptionKey: String, file: File): ByteArray {
val inputStream = BufferedReader(InputStreamReader(FileInputStream(file), Charsets.UTF_16BE))
var x: String? = ""
var builder = StringBuilder()
while (x != null) {
x = inputStream.readLine()
if (x != null)
builder.append(x).append('\n')
}
val text = builder.toString()
val read = text.toByteArray().decrypt(decryptionKey)
val path = Environment.getExternalStorageDirectory().absolutePath + "/folder"
val dir = File(path)
if (!dir.exists())
dir.mkdirs()
val outfile = File(dir, "output.pdf")
val dos = OutputStreamWriter(FileOutputStream(outfile))
dos.write(read.toString(Charsets.UTF_16LE))
dos.flush()
dos.close()
return read
}
//extension function for decrypting the byte array
private fun ByteArray.decrypt(decryptionKey: String): ByteArray {
val secretKeySpec = SecretKeySpec(decryptionKey.toByteArray(), encryptionAlgorithm)
val cipher = Cipher.getInstance(encryptionAlgorithm + encryptionMode + paddingMode)
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec)
return cipher.doFinal(this)
}
As I am new in cryptography I am not really sure where i am going wrong but i think it has something to do with file formats and encoding.
The files get decrypted without any errors but still the following exception is thrown when i try to open file in code or from the file explorer.
E/PDFView: load pdf error
java.io.IOException: cannot create document: File not in PDF format or corrupted.
at com.shockwave.pdfium.PdfiumCore.nativeOpenMemDocument(Native Method)

Saving file in internal storage using Uri (obtained from Storage Access network)

I'm using Storage Access Network to pick file and save in internal storage so that app can use if in future.
I'm getting URI without any issues. It's something like content://com.android.providers.media.documents/document/image%3A141274
Problem comes when I'm trying to save image into internal directory. Code passes without crashes, image with same size is saved into internal directory (I can see it in device Explorer: https://take.ms/3TwBS).
But image itself is broken and can't be opened.
Here's code I'm using (after getting URI)
val destinationFile = File("${context.filesDir.absolutePath}/$fileName")
try {
val writer = FileWriter(destinationFile)
writer.append(readTextFromUri(it))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
#Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
val inputStream = activity!!.contentResolver.openInputStream(uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String? = null
while ({ line = reader.readLine(); line }() != null) {
stringBuilder.append(line)
}
inputStream?.close()
reader.close()
return stringBuilder.toString()
}
As #CommonsWare described I should have used proper dealing with files, not texts.
Proper way to do:
private fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
val output = FileOutputStream(File("${filesDir.absoluteFile}/magic.png"))
inputStream?.copyTo(output, 4 * 1024)
}
Or longer way (without extension functions)
fun inputStreamToFile(uri: Uri){
val inputStream = contentResolver.openInputStream(uri)
inputStream.use {
val directory = getDir("test", Context.MODE_PRIVATE)
val file = File(directory, "correct.txt")
val output = FileOutputStream(file)
output.use {
val buffer = ByteArray(4 * 1024) // or other buffer size
var read: Int = inputStream?.read(buffer) ?: -1
while (read != -1) {
output.write(buffer, 0, read)
read = inputStream?.read(buffer) ?: -1
}
output.flush()
}
}
}

Categories

Resources