I am trying to fetch a JSONObject "image" that is sometimes a null value, so I set a try {} catch {} for my val jsonMedia in order to make it nullable but Im not sure if I'm missing something because it doesnt seem to work.
Whenever I comment out the jsonMedia my fetch works fine so I'll focus on just sharing the fetch of the "image" so the data I share doesnt get confusing.
1st. Ill show how the "image" is in the database and what I mean by "sometimes" has a null value
- null:
{
"listCar": [
{
//other data
"image": null,
//other data
}
]
}
when it contains an image:
{
"listCar": [
{
//other data
"image": {
"path": "Upload/UploadCarMain/UploadCarMain-200-200/30032235992008220498istanbul_adalarCar195.jpg",
"name": "30032235992008220498istanbul_adalarCar195.jpg"
},
//other data
}
]
}
Now my fetch data was followed by a course I'm watching the only difference is the values of "image" in the database Im using arent like the course which did not contain null values in their image path
Now here is my "Car" class that is supposed to get the image:
class Car(other data, val image: String?, other data) {
override fun toString(): String {
return "Car(//other data, image='$image', //other data')"
}
}
and here is my fetch code where I am trying to try {} catch(){} my "jsonMedia" image JSONOBject:
override fun doInBackground(vararg params: String?): ArrayList<Car> {
Log.d(TAG, "doInBackground starts")
val carList = ArrayList<Car>()
try {
val jsonData = JSONObject(params[0])
val itemsArray = jsonData.getJSONArray("listCar")
for (i in 0 until itemsArray.length()) {
val jsonCar = itemsArray.getJSONObject(i)
//other fetch data
val jsonMedia: JSONObject? = try {jsonCar.getJSONObject("image")} catch (e: IllegalArgumentException) {null}
val photoURL = jsonMedia?.getString("path")
val carObject = Car(other data, photoURL)
carList.add(carObject)
Log.d(TAG, ".doInBackground $carObject")
}
} catch (e: JSONException) {
e.printStackTrace()
Log.e(TAG, "doInBackground: Error processing json data. ${e.message}")
cancel(true)
listener.onError(e)
}
Log.d(TAG, ".doInBackground ends")
return carList
}
override fun onPostExecute(result: ArrayList<Car>) {
Log.d(TAG, "onPostExecute starts")
super.onPostExecute(result)
listener.onDataAvailable(result)
Log.d(TAG, ".onPostExecute ends")
}
}
and here is the error I get when i run the code:
2020-02-14 16:42:16.483 26642-26684/com.example.wasit E/GetFlickrJsonData: doInBackground: Error processing json data. Value null at image of type org.json.JSONObject$1 cannot be converted to JSONObject
Just modify the code like this. Exception class is the Parent class of all exception and provide exact cause of error.
for (i in 0 until itemsArray.length()) {
try {
val jsonCar = itemsArray.getJSONObject(i)
//other fetch data
val jsonMedia: JSONObject? = jsonCar.getJSONObject("image")
val photoURL = jsonMedia?.getString("path")
val carObject = Car(other data, photoURL)
carList.add(carObject)
} catch (ex: Exception) {
Log.d(TAG, ex.localizedMessage.toString())
}
}
I found a way.
I had to wrap my "jsonMedia" in the following try catch:
val jsonMedia: JSONObject? = try {jsonCar.getJSONObject("image")} catch (e: JSONException) {
null
}
Now my images that are null contain placeholders and the others work fine!
Related
Before in other random languages I always returned values from functions and I was so surprised now when I try do like below but got error:
fun getChannels(): List<TblChannel> {
val stringRequest = JsonObjectRequest(
Request.Method.GET, "$baseUrl/api/json/channel_list.json",
null,
{ response ->
try{
val gson = Gson()
val token = TypeToken.getParameterized(ArrayList::class.java,TblChannel::class.java).type
val channels1:JSONArray = response.getJSONArray("groups").getJSONObject(0).getJSONArray("channels")
//got "return isn't allowed here" error
return gson.fromJson(channels1.toString(),token)
} catch (e:Exception){
Log.e(tag,"DkPrintError on getChannels: $e")
}
},
{ error ->
Log.e(tag, "DkPrintError on getChannels: $error")
})
requestQueue.add(stringRequest)
}
How can I convert response body to my class and return them?
This isn't really a kotlin problem, we do have functions that return values, however you cannot return a value from asynch function (which is the case here):
If you perform some calculation asynchronously, you cannot directly return the value, since you don't know if the calculation is finished yet. You could wait it to be finished, but that would make the function synchronous again. Instead, you should work with callbacks
source
what you could do tho (as suggested in the quote), is use callbacks, as shown here
That post will be so helpfull to solve that problem.
In that case I solved the problem with callback method and my code was like below:
fun getChannels(onDataReadyCallback: OnDataReadyCallBack){
val stringRequest = JsonObjectRequest(
Request.Method.GET, "$baseUrl/api/json/channel_list.json",
null,
{ response ->
try{
val gson = Gson()
val token = TypeToken.getParameterized(ArrayList::class.java,TblChannel::class.java).type
val channels1:JSONArray = response.getJSONArray("groups").getJSONObject(0).getJSONArray("channels")
onDataReadyCallback.onDataReady(gson.fromJson(channels1.toString(),token))
} catch (e:Exception){
Log.e(tag,"DkPrintError on getChannels: $e")
}
},
{ error ->
Log.e(tag, "DkPrintError on getChannels: $error")
})
requestQueue.add(stringRequest)
}
and I called that fun like:
private fun getChannels(){
viewModelScope.launch {
channelsLiveData.value=roomRepository.getAllChannels
if (channelsLiveData.value.isNullOrEmpty()){
remoteRepository.getChannels(object :OnDataReadyCallBack{
override fun onDataReady(data: List<TblChannel>) {
viewModelScope.launch {
channelsLiveData.value=data
}
}
})
}
}
}
Here's a simple function that convert a string to currency format.
fun String.toCurrency(): String{
return try {
DecimalFormat("###,###").format(this.replace(",","").toInt())
} catch (E: NumberFormatException) {
this
}
}
And I want to test this method. So, I did
#Test(expected = NumberFormatException::class)
#Throws(NumberFormatException::class)
fun convertCurrency_returnAmericanFormat() {
val currentList = listOf("0", "1", "10", "100", "1000", "10000", "100000", "1000000", "100000000")
val expectedList = listOf("0", "1", "10", "100", "1,000", "10,000", "100,000", "1,000,000", "100,000,000")
currentList.forEachWithIndex { i, s ->
assertEquals(expectedList[i], s.toCurrency())
}
val exceptionList = listOf("!", "#")
exceptionList.forEach {
try {
it.toCurrency()
}catch (e: NumberFormatException){
assertEquals(NumberFormatException::class, e)
}
}
}
It didn't work and shows failure.
How can I pass the test case? I don't need to check the message but just ExceptionClass.
In toCurrency extension function, you are catching the number format exception and returning the original string. You should rethrow the exception or not to catch it at all.
// ...
catch(e: NumberFormatException) {
// log the exception or ...
throw e
}
Here, let's test only invalid scenario for toCurrency method. It would be easy to determine which test scenario failing or successing.
#Test
fun convertCurrency_invalidNumbers_throwsException() {
val invalidCurrencies = listOf("!", "#")
// check also for big numbers -> 999999999999
assertThrows<NumberFormatException> {
invalidCurrencies.forEach {
it.toCurrency()
}
}
}
#Test
#Throws(NumberFormatException::class)
fun convertCurrency_returnAmericanFormat() {
// test for successful toCurrency conversion
}
I have an API response. When data is purchased it gives as a JSONObject else a null string.
How do I process both the data types.
If I try to specify Any as the data type is model class, I am not able to retrieve the data in the JSONObject.
If it's just a null, you can simply check if the key exists before calling getString:
private fun JSONObject.getStringOrNull(key: String): String? {
return when {
this.has(key) -> try { getString(key) } catch (e: Exception) { null }
else -> null
}
}
#Test
fun runTest() {
val json = """
{ "name":"Bob Ross", "email":null }
""".trimIndent()
val obj = JSONObject(json)
val name = obj.getStringOrNull("name")
val email = obj.getStringOrNull("email")
println(name)
println(email)
}
You can use JsonElement to store the data in the model class.Since primitive data types also extend JsonElement
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()
}
I'm using mvvm , kotlin , retrofit and courtin in my app . I've done several request and all of them works fine but with this one , I get this error "Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter list"
this is my json
{
"roomslist": [
{
"id": "1"
}
]
}
these are my models
data class RoomsListModel(
#Json(name = "roomslist")
val roomsList: List<Rooms>
)
data class Rooms(
#Json(name = "id")
val id: String
}
this is my api interface :
#FormUrlEncoded
#POST("getPlaceRooms.php")
fun getPlaceRooms2(#Field("amlakid")id:String):Deferred<RoomsListModel>
this is my repository :
fun getRooms(
amlakId: String
): MutableLiveData<RoomsListModel> {
scope.launch {
val request = api.getPlaceRooms2(amlakId)
withContext(Dispatchers.Main) {
try {
val response = request.await()
roomsLiveData.value = response
} catch (e: HttpException) {
Log.v("this", e.message);
} catch (e: Throwable) {
Log.v("this", e.message);
}
}
}
return roomsLiveData;
}
when the app runs , it goes into e: Throwable and returns the error
my viewmodel
class PlacesDetailsViewModel : ViewModel() {
private val repository = PlaceDetailsRepository()
fun getRooms(amlakId: String):MutableLiveData<RoomsListModel> {
return repository.getRooms(amlakId)
}
}
and this my activity request :
viewModel.getRooms(amlakId).observe(this, Observer {
vf.frm_loading.visibility = View.GONE
it?.let {
adapter.updateList(it?.roomsList)
setNoItem(false)
}
})
I'm using moshi
I've tried to clean ,rebuild but it doesn't make any different
could you help me ?
what is going wrong with my code?
You should try adding ? to your Model parameters. Not sure if in your case is the String?. It will ensure that you can have null values on your String
val id: String?
Please double check, whatever value is missing or null in your case
Have you tried removing #Json annotation in your val id: String declaration?