I have the following methods on my code:
fun saveArticles(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val articles = Select.from(Article::class.java).list()
val iterator = articles.iterator()
while (iterator.hasNext()) {
val article = iterator.next() as Article
if (!active.contains(Regex(article.id.toString()))) article.delete()
}
}
fun saveDossiers(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val dossiers = Select.from(Dossier::class.java).list()
val iterator = dossiers.iterator()
while (iterator.hasNext()) {
val dossier = iterator.next() as Dossier
if (!active.contains(Regex(dossier.id.toString()))) dossier.delete()
}
}
fun saveVideos(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val videos = Select.from(Video::class.java).list()
val iterator = videos.iterator()
while (iterator.hasNext()) {
val video = iterator.next() as Video
if (!active.contains(Regex(video.id.toString()))) video.delete()
}
}
As you can see, all the methods do exactly the same thing. The only difference is the Class type of the object I'm working at the moment. Can I somehow create a single method with a parameter of Class type, and depending of the type change the class I need to work? Something like this:
fun saveVideos(data: JSONArray, type: Class) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val list = Select.from(type).list()
val iterator = list.iterator()
while (iterator.hasNext()) {
val item = iterator.next() as type
if (!active.contains(Regex((item as type).id.toString()))) item.delete()
}
}
You need to extract an interface and use a reified generic.
interface Blabla {
fun delete()
val id: Int
}
inline fun <reified T : Blabla>saveVideos(data: JSONArray) {
var active = String()
for (i in 0..data.length().minus(1)) // create string
val list = Select.from(T::class.java).list()
val iterator = list.iterator()
while (iterator.hasNext()) {
val item = iterator.next() as T
if (Regex(item.id.toString()) !in active) item.delete()
}
}
This should work.
Also, I highly recommend you to use the Kotlin collection library, like this.
inline fun <reified T : Blabla>saveVideos(data: JSONArray) {
val active = ""
for (i in 0 until data.length()) {} // create string
val list = Select.from(T::class.java).list()
list
.map { it as T }
.filter { Regex(it.id.toString()) !in active }
.forEach { it.delete() }
}
And you can even replace forEach { it.delete() } with forEach(T::delete)
Related
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>())
}
}
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
}
Context
Using a declarative approach in Kotlin, need to copy a single name property from List of User objects to a List of UserDetail objects based on matching id properties as shown below:
val users = Arrays.asList(
User(1, "a"),
User(2, "b")
)
val details = Arrays.asList(
UserDetail(1),
UserDetail(2)
)
val detailsWithName = copyNameToUser(users, details)
Models are:
class User {
var id = -1;
var name = "" // given for all Users
constructor(id: Int, name: String)
// ...
}
class UserDetail {
var id = -1;
var name = "" // blank for all UserDetails
constructor(id: Int)
// ...
}
Problem
Tried to use a declarative approach via forEach iterable function:
fun copyNameToDetails(users: List<User>, details: List<UserDetail>): List<UserDetail> {
details.forEach(d ->
users.forEach(u ->
if (d.id == u.id) {
d.name = u.name
}
)
)
return details
}
This can be achieved in Java as shown below:
private static List<UserDetail> copyNameToDetails(List<User> users, List<UserDetail> details) {
for (UserDetail d: details) {
for (User u : users) {
if (d.id == u.id) {
d.name = u.name;
}
}
}
return details;
}
Question
How can this be done in Kotlin using a declarative approach?
You make too many iterations over both lists (users.size * details.size) so creating a hashmap can fix it a bit:
fun copyNameToUsers(users: List<User>, details: List<UserDetail>): List<UserDetail> {
val usersById = users.associate { it.id to it }
details.forEach { d ->
usersById[d.id]?.let { d.name = it.name }
}
return details
}
An other approach with non mutable values :
data class User(val id: Int = -1, val name: String = "")
data class UserDetail(val id: Int = -1, val name: String = "")
private fun List<UserDetail>.copyNameToUser(users: List<User>): List<UserDetail> = map { userDetail ->
users.firstOrNull { userDetail.id == it.id }?.let { userDetail.copy(name = it.name) } ?: userDetail
}
I have following data:
var dict: Map<String, Any> = listOf()
dict["p1"] = listOf(1, 3)
dict["p2"] = listOf(null, 2.1)
dict["p3"] = 1
When I pass this data to following function:
#GET("uri?staticKey=staticValue")
fun testApi(#QueryMap(encoded = true) params: #JvmSuppressWildcards Map<String, Any>): Call<ResponseBody>
I expect the request URL to be:
uri?staticKey=staticValue&
p1[0]=1&p1[1]=3&
p2[0]=&p2[1]=2.1&
p3=1
But here is what it produces:
uri?staticKey=staticValue&
p1=[1, 3]&
p2=[null, 2.1]&
p3=1
Am I doing something wrong? I have just started with Kotlin and Android development, so I am not sure if this is supported by Retrofit/okhttp library.
Note I need such feature as Map<String, Any> in order to easily add/delete query params.
Retrofit just calls toString() on the value of the Map<String, Any>. To achieve what you intend, you have to define it directly
var dict: Map<String, Any> = listOf()
dict["p1[0]"] = 1
dict["p1[1]"] = 3
dict["p2[0]"] = ""
dict["p2[1]"] = 2.1
dict["p3"] = 1
You could map the map simply by just flattening it
fun Map<String, Any>.queryArgs(): Map<String, Any> {
val map = mutableMapOf<String, Any>()
forEach { (key, value) ->
if (value !is Iterable<*>) {
map[key] = value
} else value.forEachIndexed { index, value ->
map["$key[$index]"] = value ?: ""
}
}
return map
}
This assumes only one level of lists at most.
Here is my solution that offers
Original keys in HashMap, so that updating/deleting is possible, such as
dict.remove("p1")
dict["p1"] = new value
Ability to have nested arrays, such as
[
0: [coord1, coord2, coord3],
1: [coord1, coord2, coord3]
]
data class URL(val params: MutableMap<String, Any> = mutableMapOf<String, Any>()) {
override fun toString(): String = params.map {
val value = it.value
if (value !is Iterable<*>) {
it.key + "=" + it.value.toString()
} else {
createPairs(it.key, value)
}
}.joinToString("&")
fun createPairs(key: String, value: Iterable<*>): String {
return value.mapIndexed { idx, value ->
val useKey = key + "[" + idx + "]"
if (value !is Iterable<*>) {
useKey + "=" + (value?.toString() ?: "")
} else
createPairs(useKey, value)
}.joinToString("&")
}
}
Usage:
val url = URL()
url.params["p1"] = listOf(1, 3)
url.params["p2"] = listOf(null, 2.1)
url.params["p3"] = 1
val polygon1 = listOf("p1-coord1", "p1-coord2")
val polygon2 = listOf("p2-coord1", "p2-coord2", "p2-coord3")
val polygon3 = listOf("p3-coord1", "p3-coord2")
url.params["bbox"] = listOf(polygon1, polygon2, polygon3)
println(url.toString())
Output:
p1[0]=1&p1[1]=3&
p2[0]=&p2[1]=2.1&
p3=1&
bbox[0][0]=p1-coord1&bbox[0][1]=p1-coord2&
bbox[1][0]=p2-coord1&bbox[1][1]=p2-coord2&bbox[1][2]=p2-coord3&
bbox[2][0]=p3-coord1&bbox[2][1]=p3-coord2
I'm using Google Books API for searching books. But problem is that when I want my ListView to be populated with books, I get an error. This error is pointed onPostExecute, but I can't figured out what's the problem.
kotlin.KotlinNullPointerException
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:137)
at com.example.claudiu.reader.Fragments.ISBNFragment$FetchBookTask.onPostExecute(ISBNFragment.kt:56)
Here is where I set my adapter :
override fun onPostExecute(books: List<Book>?) {
if (books != null) {
adapter!!.clear()
for (book in books) {
adapter!!.add(book)
}
}
}
And here is all code where I'm parsing the JSON:
#Throws(JSONException::class)
private fun getBookDataFromJson(booksJsonStr: String?): List<Book> {
val books = ArrayList<Book>()
val API_RESULT_ITEMS_ARRAY = "items"
val API_VOLUME_INFO = "volumeInfo"
val API_BOOK_TITLE = "title"
val API_BOOK_IMAGE_LINKS = "imageLinks"
val API_BOOK_SMALL_THUMBNAIL = "smallThumbnail"
val API_BOOK_AUTHORS_ARRAY = "authors"
val booksJson = JSONObject(booksJsonStr)
val itemsArray = booksJson.getJSONArray(API_RESULT_ITEMS_ARRAY)
for (i in 0 until itemsArray.length()) {
val item = itemsArray.getJSONObject(i)
val volumeInfo = item.getJSONObject(API_VOLUME_INFO)
val bookTitle = volumeInfo.getString(API_BOOK_TITLE)
val imageLinksSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_IMAGE_LINKS)) {
val imageLinks = volumeInfo.getJSONObject(API_BOOK_IMAGE_LINKS)
imageLinksSB.append(imageLinks.getString(API_BOOK_SMALL_THUMBNAIL))
} else {
imageLinksSB.append("-1")
}
val bookImageLink = imageLinksSB.toString()
val authorsSB = StringBuilder()
if (!volumeInfo.isNull(API_BOOK_AUTHORS_ARRAY)) {
val authorsArray = volumeInfo.getJSONArray(API_BOOK_AUTHORS_ARRAY)
for (k in 0 until authorsArray.length()) {
authorsSB.append(authorsArray.getString(k))
authorsSB.append(getString(R.string.comma))
}
} else {
authorsSB.append(getString(R.string.unknown_error))
}
val bookAuthors = authorsSB.toString()
books.add(Book(bookTitle, bookAuthors, bookImageLink))
}
Log.d(LOG_TAG, "BOOKS : $books")
return books
}
I couldn't find any thing to help me and I have no idea what should I do.
Your problem is the place where you declare your adapter, I had this problem because declaration of my adapter was in the wrong place.
You should move adapter declaration from onCreatView into onViewCreated and everything will work fine.
Hope it helps !