This is with implementation 'org.json:json:20180813'
So I have a User class that allows itself to be translated to a json string for persistence:
data class User(override val name:String, override val creds:ICredentials) : IUser, isEncodable<IUser>{
override val isLoggedIn by lazy { creds is Credentials.Valid }
override fun encode():String{
val credsEncoding = creds.encode()
return JSONStringer().withObject { it
.key("type").value(this::class.java.name)
.key("name").value(name)
.key("creds").value(credsEncoding)
}.toString()
}
}
where withObject is an extension function:
fun JSONWriter.withObject(content:(JSONWriter)->JSONWriter) = content(`object`() as JSONWriter).endObject()
This seems to compile and work perfectly fine.
However, AndroidStudio marks it red and claims
Unresolved reference
None of the following candidates is applicable because of receiver type mismatch
public fun JSONWriter.withObject(content:(JSONWriter)->JSONWriter):JSONWriter! defined in [...]`
If I try to "cheat" and write it as
(JSONStringer() as JSONWriter).withObject { it
...
}
the error becomes a warning that
This cast can never succeed.
But apparently, it does.
Why is that? And how do I get rid of this "error"?
This is with implementation 'org.json:json:20180813'
That's unlikely to work well.
Why is that?
For the past 11 years, Android has a copy of org.json classes in the Android SDK. This includes JSONStringer. You can't have two classes with the same fully-qualified name, and the firmware always wins. Your copy of the conflicting class will not be used, with the firmware one used instead. And the firmware's API has not changed much in those past 11 years.
And how do I get rid of this "error"?
Remove implementation 'org.json:json:20180813'. Either:
Use the Android SDK's built-in org.json classes, or
Use the Android SDK's JsonReader and JsonWriter classes, or
Use a different JSON parser (e.g., Gson, Jackson, Moshi)
Related
I'm using #RestrictTo annotation to denote that a function should be used in only in subclasses or tests.
To do that I use the following syntax:
#RestrictTo(value = [SUBCLASSES, TESTS])
public override fun onCleared() {
// Expose protected fun onCleared for tests
}
At first it seemed to be working but my teammates reported Android Studio showing this warning:
I could reproduce this after building the project again.
This error goes away if I remove the TESTS scope from the annotation as if the annotation does not support multiple scopes in values.
Do you think if this is the intended behavior of the annotation?
Can you think another way to restrict a function to the union of two different scopes?
Thanks in advance
We are a company with 10+ apps all using 10+ internal SDKs, all coded back at the glorious time of the kotlin-android-extensions gradle plugin. We heavily rely on both #Parcelize and synthetic binding (all our views are coded with it). With the newer versions of Kotlin, this plugin is now deprecated in favor of kotlin-parcelize for #Parcelize and View Binding as a replacement for synthetic binding.
We tried to upgrade our Kotlin version from 1.4.10 to 1.6.0 while still using the kotlin-android-extensions plugin. We suddenly had a #Parcelize error reported here, fixed in Kotlin 1.5.0. Except that the fix is not in Kotlin itself, it is in the kotlin-parcelize plugin. And of course as they deprecated kotlin-android-extensions, the later doesn't contain the fix. So in short, in order to fix the issue we have to use kotlin-parcelize.
Knowing that kotlin-parcelize can't be used along kotlin-android-extensions (build error), and that switching all our views from synthetic to view binding will be a hell of a work that will take a tremendous amount of time, what can be done here ? We really want to keep using synthetic binding while being able to upgrade Kotlin to its latest versions.
What looks like the obvious choice is that you need to move from synthetic views as soon as possible although it might not be possible due to lack of resources.
Old-school approach
A solution to get around the Parcelable problem is to figure out the classes that are not properly parcelized and serialize them in an old-school fashion.
Serializing to JSON
I suggest you take a look at the kotlinx-serialization package it's quite handy to read from and write to JSON.
For those that don't work you can provide the following parcelable implementation which would be generic and work all around the codebase:
Ensure that your classes are annotated with #Serializable important that such annotation comes from kotlinx.serialization package and not the java one.
With this generic function you can write any #Serializable object into a parcel.
inline fun <reified T> writeToParcel(out: Parcel, data: T) {
val jsonString = Json.encodeToString(data)
out.writeInt(jsonString.length)
out.writeByteArray(jsonString.toByteArray())
}
And with this other one you should be able to deserialize such object
inline fun <reified T> readFromParcel(input: Parcel): T {
val size = input.readInt()
val bytes = ByteArray(size) { input.readByte() }
val jsonString = bytes.toString()
return Json.decodeFromString<T>(jsonString)
}
You can make them extension function from Parcel to write even less code.
This should get you around the classes that are problematic for the Parcelize extension; it's not as fast as a proper Parcelable implementation but hey, it works.
Side notes
Note that the code has written directly as an answer of this post and hence untested and some parts might need adaptation. And of course, check out the kotlinx.serialization documentation to see how to create the Json encoder and decoder (it's fairly simple)
I've refactored some parts of my code which is written in Kotlin and tend to put url's in strings.xml, but when I want to point to the string in strings.xml file in annotation part of the Retrofit, I get the following error.
An annotation argument must be a compile-time constant
Here is my code:
interface SampleApiService {
#GET(Resources.getSystem().getString(R.string.sample_url))
fun getSamples(){
}
}
Could anyone please tell me what is wrong?
Found the answer in the following post.
As documentation states it compiles your application resources at build time. and Annotation processing takes place at compile time. So you see you cannot pass resources string to a annotation
The Android SDK tools compile your application's resources into the
application binary at build time.
instead create static string variable and pass it in annotation
Problem solved,
Tnx to Retrofit 2 - Dynamic URL
I had to use another annotation mark of retrofit.
New: #Url parameter annotation allows passing a complete URL for an endpoint.
Here is the result:
interface SampleApiService {
#GET
fun getSamples(
#Url url: String =
Resources.getSystem().getString(R.string.sample_url)
)
: Deferred<ArrayList<Sample>>
}
Based upon the example provided - one could as well just use a static URL. It would need to be annotated alike this, in order not to use any run-time values and be able to change it at run-time:
#GET("{path}")
fun getSamples(#Path("path") path: String) {}
Then one can load whatever String path from string resources, at run-time. When the base URL shall be changed, one may need to reconfigure the client. That is because this interface definition is being used by the annotation processor to generate the abstraction layer from it - at compile time already, not at run-time... when taking the complaint literal, it would have to look alike this:
#GET(Constants.SOME_PATH)
fun getSamples() {}
but there is little advance over just hard-coding that String, because it cannot be changed later.
I use kotlinx.serialization on Kotlin native project, I a defined Super class for my models and all of the models extends from it.
I defined a function to called toJSON() for serialize variables and fields inside model that all of class models have it.
#Serializable
open class Model {
fun toJSON(): String = JSON.stringify(this);
}
And I created a subclass
class Me : Model() {
var name:String = "Jack";
}
but when I invoke JSON.stringify(this), IDE get a Warning to me:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
I paid attention and I used #ImplicitReflectionSerializer annotation while not worked.
Where is my problem?
This is discussed here. It's the particular overload you're using which is still experimental. So your options are either to use the other overload (which takes in a serializer) or to use one of the annotations mentioned in the error message. If you look at the answer to the question I linked (and the comments following it), you'll see it talks about using #UseExperimental and where it should be used.
In my Kotlin Multiplatform project, I'm trying to access Kotlin types defined in kotlin-stdlib from Swift.
TL;DR: StdLib types/methods seem not to result in header definitions, I'd like a solution that doesn't involve writing lots of boilerplate code
My scenario
I have an interface defined in Kotlin ...
interface MyKotlinInterface {
fun run() : Sequence<String>
}
... and implemented this interface in Swift ...
class MySwiftClass : MyKotlinInterface {
func run() -> KotlinSequence {
// return sequenceOf("foo")
}
}
... there I'm trying to create a Sequence but there are no methods from the kotlin.sequences package available (e.g. generateSequence).
Is it actually possible to access Kotlin framework types or methods beyond what I define in my code -- if yes, how? Furthermore, how can this be achieved without writing boilerplate code?
Further details
Having a look into the generated Objective-C header file, I see definitions for my class (obviously) and basic Kotlin types. What's missing is basically everything from the standard library functionality (I care for everything Sequence-related).
My build.gradle.kts looks like:
plugins {
kotlin("multiplatform") version "1.3.0"
}
kotlin {
targets { /* ... */ }
sourceSets {
getByName("commonMain") {
dependencies {
api("org.jetbrains.kotlin:kotlin-stdlib-common")
}
}
// ...
getByName("iosMain") {
dependencies {
api("org.jetbrains.kotlin:kotlin-stdlib")
}
}
}
}
Having the kotlin-stdlib defined as a dependency for the iOS target, I would expect those to become actually available from Swift.
Minimal working example
https://github.com/panzerfahrer/so-mwe-kotlin-mpp-swift
Current solution approach
The only solution I came up with, is writing the desired function for the iOS target:
fun <T : kotlin.Any> generateSequence(nextFunction: () -> T?): kotlin.sequences.Sequence<T> = kotlin.sequences.generateSequence(nextFunction)
This works ok-ish but is highly unsatisfying as it requires lots of boilerplate code. Additionally, extension functions cannot be made available this way and would require more boilerplate code or even rewriting parts of the standard library.
Desired solution
I like to avoid writing boilerplate code as much as possible. What I actually only care about, is to have (in my case) Sequence fully accessible from Swift. My feeling is, it would be sufficient to make the compiler generate selected or all header definitions for the standard library functionality.
Do you really need lazy computation (aka Sequence) in your Kotlin code?
If no, I would recommend using List<T> instead (and it maps to Swift directly).
For Sequence implementation, a workaround could be to export a factory function from your Kotlin library, e.g. you may declare a function like
fun <T : kotlin.Any> generateSequence(nextFunction: () -> T?)
= kotlin.sequences.generateSequence(nextFunction)
You may select any other factory function for Sequence, that matches your use-case.
In general, there are too many functions in the Kotlin standard library. Exporting them all to Swift will create too many useless symbols in the binary and increase the compilation time.