Accessing local variable in lambda in Kotlin - android

I want to save data acquired from Volley, But lambda used in VolleyRequest function(which gets json data from server) blocks it.
How should I change local variable that is in outside of lambda?
Thanks in advance.
class ConDataforReturn( val title:String , val imgDataList: ArrayList<ConImgData>)
fun getConData(context: Context, idx : String):ConDataforReturn{
val params = HashMap<String,String>()
var cd = arrayListOf<ConImgData>()
var title =""
params.put("package_idx",idx)
Log.e("idx size",idx.length.toString())
VolleyRequest(context,params,"https://dccon.dcinside.com/index/package_detail") { response ->
val answer = JSONObject(response)
var json = answer.getJSONArray("detail")
title = answer.getJSONObject("info").getString("title")
Log.d("title",title)//Prints right data
for (i in 0..(json.length() - 1)) {
val v = json.getJSONObject(i)
cd.add(ConImgData(v.getString("title"), v.getString("ext"), v.getString("path")))
}
}
return ConDataforReturn(title,cd)//returns ConDataforReturn("",arrayListOf<ConImgData>())
}

Here the the code from were you are calling this method
getConData(this, "id") { condata ->
}
Now, your method look like this,
fun getConData(context: Context, idx : String, returnConData : (condata : ConDataforReturn) -> Unit){
val params = HashMap<String,String>()
var cd = arrayListOf<ConImgData>()
var title =""
params.put("package_idx",idx)
Log.e("idx size",idx.length.toString())
VolleyRequest(context,params,"https://dccon.dcinside.com/index/package_detail") { response ->
val answer = JSONObject(response)
var json = answer.getJSONArray("detail")
title = answer.getJSONObject("info").getString("title")
Log.d("title",title)//Prints right data
for (i in 0..(json.length() - 1)) {
val v = json.getJSONObject(i)
cd.add(ConImgData(v.getString("title"), v.getString("ext"), v.getString("path")))
}
returnConData(ConDataforReturn(title,cd)) //returns ConDataforReturn("",arrayListOf<ConImgData>())
}
}

Related

How to write and read Android Realm?

I need to cache data in Realm in Android/Kotlin project.
When I write and then read – I get nothing. Previously I could write once, so I know that reading code works. But writes do nothing. Then I reset emulator and now I can't read anything. What I do wrong?
I tried to follow and an official example and the mistake slips away from me.
I have a data class:
open class DataItemExtra: RealmObject {
#PrimaryKey
var id: String? = null
var strVal: String = ""
var intVal: Int = 0
var extra : String = "extra"
constructor(id: String?, s: String, n: Int){
this.id = id
this.strVal = s
this.intVal = n
}
constructor()
}
and a code in activity:
class MainActivity : AppCompatActivity() {
lateinit var realm: Realm
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonSet = findViewById<Button>(R.id.buttonSet)
val buttonGet = findViewById<Button>(R.id.buttonGet)
val editTextString = findViewById<EditText>(R.id.editText)
val editTextInt = findViewById<EditText>(R.id.editNum)
val textRes = findViewById<TextView>(R.id.textRes)
Realm.init(this)
val realmName = "My Project"
val config = RealmConfiguration.Builder()
.schemaVersion(1)
.deleteRealmIfMigrationNeeded()
.name(realmName)
.build()
this.realm = Realm.getInstance(config)
buttonSet.setOnClickListener{
val strVal = editTextString.text.toString()
val intVal = editTextInt.text.toString().toInt()
Toast.makeText(this, "$strVal : $intVal",Toast.LENGTH_LONG).show()
val di = DataItemExtra("keyVal", strVal, intVal)
Thread{
this.realm.executeTransaction { transactionRealm ->
transactionRealm.insertOrUpdate(di)
}
}
}
buttonGet.setOnClickListener{
val tasks : RealmResults<DataItemExtra> = this.realm.where<DataItemExtra>().findAll()
textRes.text = ""
val r = tasks.toArray()
val rSize = r.size
if (rSize != 1) {
textRes.text = "wrong items number $rSize"
} else {
val d = tasks[0]
textRes.text = "${d?.id}, ${d?.strVal}, ${d?.intVal}, ${d?.extra}"
}
tasks.forEach { d ->
Log.d("REALM-RES","${d.id}, ${d.strVal}, ${d.intVal}, ${d.extra}")
}
}
}
}
Well, that's some threading issues. I didn't figure out what it is yet, but the right way to write data is to run executeTransactionAsync instead of running separate thread:
realm.executeTransactionAsync { transactionRealm ->
transactionRealm.insertOrUpdate(di)
}

Kotlin returns object with unassigned properties after assigning them in apply function

I'm trying to do a quite simple task: assign properties to an object and return that same object after retrieving the infos with a REST call.
In my runBlocking block I use the apply function to change the properties of my object, but after trying different ways to assign them, instantiate the object itself, modifying constructing logic of the object, I still get an object with the default values.
Here's my Info object:
class DrivingLicenceInfo {
var type : String = ""
var nationality : String = ""
var number : String = ""
var releaseDate : String = ""
var expiryDate : String = ""
}
Here's the method which gives me problems:
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
var info = DrivingLicenceInfo()
response.body()?.let {
info.apply {
it.data.let { data ->
val type = data.guy
val drivingLicenseNationality = data.drivingLicenseNationality
val drivingLicenseNumber = data.drivingLicenseNumber
val drivingReleaseDate = data.drivingReleaseDate
val drivingExpiryDate = data.drivingExpiryDate
this.type = type
this.nationality = drivingLicenseNationality
this.number = drivingLicenseNumber
this.releaseDate = drivingReleaseDate
this.expiryDate = drivingExpiryDate
}
}
info
Log.i("driving.info.call", info.type)
}
}
DrivingLicenceInfo()
}
}
And here's where I use it, in my Main, and where I get an info object with empty strings as properties
private void getDrivingLicenceData() {
DrivingLicenceInfoService service = new DrivingLicenceInfoServiceImpl(context);
DrivingLicenceInfo info = service.getDrivingLicenceInfo();
Log.i("driving.info.main",info.getType());
profileViewModel.licenceNumber.postValue(info.getNumber());
profileViewModel.licenceExpiryDate.postValue(info.getExpiryDate());
}
The log in the runBlocking correctly shows the property, the log in my Main doesn't even show up.
Using the debugger I am able to see that info has empty strings as value.
Could somebody help me to figure out what I'm doing wrong?
Thank you
Beside #JeelVankhede giving you the main reason for your problem, I suggest some minor code improvements as well. I personally feel this is ways less verbose and better readable
private fun getDrivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
return runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
var info = DrivingLicenceInfo()
return if (response.isSuccessful) {
response.body()?.let {
info.apply {
type = it.data.guy
nationality = it.data.drivingLicenseNationality
number = it.data.drivingLicenseNumber
releaseDate = it.data.drivingReleaseDate
expiryDate = it.data.drivingExpiryDate
}
Log.i("driving.info.call", info.type)
info
} ?: info
} else { info }
}
}
Since #JeelVankhede already told you the main reason of your problem and I also have some suggestions apart from the one given by #WarrenFaith.
If DrivingLicenceInfo is a model class you can declare it as data class like
data class DrivingLicenceInfo (
val type : String = "",
val nationality : String = "",
val number : String = "",
val releaseDate : String = "",
val expiryDate : String = ""
)
you can read more about data class here.
And then you can write your function as
private fun getDerivingLicenceInfoAndWaitForCompletion(): DrivingLicenceInfo {
val info = runBlocking {
val response = retrieveDrivingLicenceInfoAsync().await()
if (response.isSuccessful) {
response.body()?.let {
it.data.let { data ->
DrivingLicenceInfo(
type = data.guy,
nationality = data.drivingLicenseNationality,
number = data.drivingLicenseNumber,
releaseDate = data.drivingReleaseDate,
expiryDate = data.drivingExpiryDate
)
}
} ?: DrivingLicenceInfo()
} else {
DrivingLicenceInfo()
}
}
Log.i("driving.info.call", info.type)
return info
}

How to show a progress during fetching JSON-data

I would like to fetch some data from my web server. The data is a JSON object. I fetch the data with the fuel framework. I want to fill a recyclerview with the data. But I want to show a progress until the data is fetched.
But I have no idea how to solve this with fuel.
I have studied the fuel documentation. But I cannot find a solution
The code fetches some JSON data
fun fetchMyThings(): List<Thing> {
val username = "xxxx"
val password = "xxxxxxxxx"
val things = mutableListOf<Thing>()
Fuel.get("https://www.thingurl.com/things")
.authentication()
.basic(username, password)
.responseJson { request, response, result ->
request.responseProgress()
val arr: JSONArray = result.get().array()
println(arr.length())
for (i in 0 until arr.length()) {
var elem: JSONObject = arr[i] as JSONObject
var obj: JSONObject = elem
var thing = Thing(UUID.fromString(obj.getString("uuid")))
thing.summary = obj.getString("summary")
things += thing
println(thing)
}
}
return things
}
This code fills the recyclerview
private fun fillRecyclerView(things : List<Thing>) {
val recyclerView = findViewById<RecyclerView>(R.id.main_recycler)
val mainActivityRecyclerAdapter = MainActivityAdapter(this, things)
recyclerView.adapter = mainActivityRecyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
}
Expectation: Progresshandling with Fuel
Many thanks in advance for any help
Next try...
fun getThings(): Single<Result<String, FuelError>> {
val username = "********"
val password = "********"
val http = "https://www.nowhere.com/thing/"
.httpGet()
.authentication()
.basic(username, password)
.rxString(Charsets.UTF_8)
return http
}
class MainActivity : AppCompatActivity() {
var things: List<Thing> = emptyList()
protected lateinit var adapter: MainActivityAdapter
override fun onCreate(savedInstanceState: Bundle?) {
//....
fillRecyclerView(things)
Server.getThings().subscribe { p ->
println(p)
val arr: JSONArray = JSONArray(p.component1())
for (i in 0 until arr.length()) {
var elem: JSONObject = arr[i] as JSONObject
var obj: JSONObject = elem
var thing = Thing(UUID.fromString(obj.getString("uuid")))
thing.summary = obj.getString("summary")
things += thing
println(thing)
}
adapter?.notifyDataSetChanged()
findViewById<ProgressBar>(R.id.ac_main_progress).visibility = View.GONE
}
private fun fillRecyclerView(things : List<Thing>) {
val recyclerView = findViewById<RecyclerView>(R.id.main_recycler)
val mainActivityRecyclerAdapter = MainActivityAdapter(this, things)
adapter = mainActivityRecyclerAdapter
recyclerView.adapter = mainActivityRecyclerAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
}
}
```
you can use fuel-rxjava (https://github.com/kittinunf/fuel/tree/master/fuel-rxjava) for showing progress. Register an observer to notify the progress bar using rx-java

Can't convert JSONArray into a list (Kotlin)

This is my string:
{"array":[{"message":"test1","name":"test2","creation":"test3"},{"message":"test1","name":"test2","creation":"test3"}]}
And I want it get that array into a list of object in Kotlin app for Android.
I tried to do it using two examples from this site... So here is my code (res = that string):
val gson = Gson()
val obj = gson.fromJson(res, JsonObject::class.java)
val arr = obj.getAsJsonArray("array")
println(arr.toString())
val list1 : List<JThread> = gson.fromJson(arr, object : TypeToken<List<JThread>>() {}.type)
val list2 = gson.fromJson(arr, Array<JThread>::class.java).asList()
for (x in list1){
println(x.message)
}
for (x in list2){
println(x.message)
}
However I'm only getting null in x.message. I don't know what can go wrong.
I also tried changing arr to arr.toString() everywhere and that didn't work either.
Also JThread is:
object JThread {
var message: String? = null
var name: String? = null
var creation: String? = null }
This can be done without GSON or any other third party library:
#Throws(JSONException::class)
fun JSONObject.toMap(): Map<String, Any> {
val map = mutableMapOf<String, Any>()
val keysItr: Iterator<String> = this.keys()
while (keysItr.hasNext()) {
val key = keysItr.next()
var value: Any = this.get(key)
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
map[key] = value
}
return map
}
#Throws(JSONException::class)
fun JSONArray.toList(): List<Any> {
val list = mutableListOf<Any>()
for (i in 0 until this.length()) {
var value: Any = this[i]
when (value) {
is JSONArray -> value = value.toList()
is JSONObject -> value = value.toMap()
}
list.add(value)
}
return list
}
Usage to convert JSONArray to List:
val jsonArray = JSONArray(jsonArrStr)
val list = jsonArray.toList()
Usage to convert JSONObject to Map:
val jsonObject = JSONObject(jsonObjStr)
val map = jsonObject.toMap()
More info is here
Use this code:
import com.google.gson.annotations.SerializedName
import com.google.gson.Gson
data class Array(
#SerializedName("message")
var message: String,
#SerializedName("name")
var name: String,
#SerializedName("creation")
var creation: String
)
data class Example(
#SerializedName("array")
var array: List<Array>? = null
)
private fun fromJson(json:String):Example{
return Gson().fromJson<Example>(json, Example::class.java)
}
PS: I made it with this site:http://www.jsonschema2pojo.org/

Android (Kotlin) - How do I wait for an asynchronous task to finish?

I am new to Android and Kotlin and am currently working on a centralized API router class.
To achieve this I am using the Fuel Framework.
For the doAsync function, I use the Anko for Kotlin library.
To retrieve an authorization token from the API I currently use this method:
private fun Login(username: String, password: String, callback: (Map<Boolean, String>) -> Unit) {
"/auth/token.json".httpPost()
.header(mapOf("Content-Type" to "application/json"))
.body("""{"username":"$username", "password":"$password"}""", Charsets.UTF_8)
.response { request, response, result ->
request.headers.remove("Accept-Encoding")
when (result) {
is Result.Failure -> {
// val data = result.get()
val ex = result.getException()
val serverResponseJson = response.data.toString(Charsets.UTF_8)
var exceptionMessage = ex.message
val jelement = JsonParser().parse(serverResponseJson)
val jobject = jelement.asJsonObject
val serverResponseError = if (jobject.has("Error")) jobject.get("Error").asString else jobject.get("detail").asString
callback(mapOf(Pair(false, serverResponseError)))
}
is Result.Success -> {
val data = result.get()
val returnJson = data.toString(Charsets.UTF_8)
Log.println(Log.ASSERT, "RESULT_LOGIN", returnJson)
callback(mapOf(Pair(true, returnJson)))
}
}
}
}
I invoke this login method at
val btnLogin = findViewById<Button>(R.id.btn_login)
btnLogin.setOnClickListener { _ ->
doAsync {
val username = findViewById<EditText>(R.id.input_username_login)
val password = findViewById<EditText>(R.id.input_password_login)
Login(username.text.toString(), password.text.toString()) {
// Request was successful
if (it.containsKey(true)) {
// Parse return Json
// e.g. {"id":"36e8fac0-487a-11e8-ad4e-c471feb11e42","token":"d6897a230fd7739e601649bf5fd89ea4b93317f6","expiry":"2018-04-27T17:49:48.721278Z"}
val jelement = JsonParser().parse(it.getValue(true))
val jobject = jelement.asJsonObject
// save field for class-scope access
Constants.token = jobject.get("token").asString
Constants.id = jobject.get("id").asString
}
else{
Toast.makeText(this#LoginActivity, it.getValue(false), Toast.LENGTH_SHORT).show()
}
}
}[30, TimeUnit.SECONDS]
var test = Constants.id;
}
In a separate Constants class, I store the token and id like this:
class Constants {
companion object {
val baseUrl: String = "BASE_URL_TO_MY_API"
val contentTypeJson = "application/json"
lateinit var STOREAGE_PATH: String
// current user details
lateinit var id: String
lateinit var token: String
lateinit var refresh_token: String
// logged in User
lateinit var user: User
}
How do I make sure that the test variable is set after the asynchronous task is done? Currently, I run into
lateinit property id has not been initialized
I have come across the option to limit the task to a timeout such as I have done with [30, TimeUnit.SECONDS], unfortunately, this did not help.
Thanks for the help! Cheers.
I think the problem is where you want to access the result:
val btnLogin = findViewById<Button>(R.id.btn_login)
btnLogin.setOnClickListener { _ ->
doAsync {
val username = findViewById<EditText>(R.id.input_username_login)
val password = findViewById<EditText>(R.id.input_password_login)
var test: String? = null
Login(username.text.toString(), password.text.toString()) {
// Request was successful
if (it.containsKey(true)) {
// Parse return Json
// e.g. {"id":"36e8fac0-487a-11e8-ad4e-c471feb11e42","token":"d6897a230fd7739e601649bf5fd89ea4b93317f6","expiry":"2018-04-27T17:49:48.721278Z"}
val jelement = JsonParser().parse(it.getValue(true))
val jobject = jelement.asJsonObject
// save field for class-scope access
Constants.token = jobject.get("token").asString
Constants.id = jobject.get("id").asString
}
else{
Toast.makeText(this#LoginActivity, it.getValue(false), Toast.LENGTH_SHORT).show()
}
}
test = Constants.id // here test variable surely set if result was successful, otherwise it holds the null value
test?.let{
resultDelivered(it)
}
}[30, TimeUnit.SECONDS]
}
fun resultDelivered(id: String){
// here we know that the async job has successfully finished
}

Categories

Resources