I got an Unresolved Reference for a typeAlias I use from my library.
Everything works fine in local, but when using the imported released library I got this error.
Anyone had this issue already please ?
Here is my code :
sealed class CountingRequestResult<ResultT> {
data class Progress<ResultT>(
val progressFraction: Double
) : CountingRequestResult<ResultT>()
data class Completed<ResultT>(
val result: ResultT
) : CountingRequestResult<ResultT>()
}
typealias AttachmentUploadRemoteResult = CountingRequestResult<UploadUserDocumentResponse>
Kotlin, like other programming languages, lets you define a type alias for other existing types. For instance, we can use them to attach more context to an otherwise general type, like naming type String as UserName or Password:
typealias UserName = String
typealias Password = String
fun checkUserName(userName: UserName) {
// some code here
}
fun checkPassword(password: Password) {
// some code here
}
Type aliases are merely artifacts of the source code. Therefore, they’re not introducing any new types at runtime. For instance, every place where we’re using a UserName instance, the Kotlin compiler translates it to a String:
$ kotlinc TypeAlias.kt
$ javap -c -p com.example.alias.TypeAliasKt
Compiled from "TypeAlias.kt"
public final class com.example.alias.TypeAliasKt {
public static final void checkUserName(java.lang.String);
// truncated
}
Therefore, in local everything works as expected, but building a library results in losing the aliasing, hence: Unresolved Reference.
In your project using the lib, you can define the same alias name. However I will say that it is not such a good idea. Making a change in the lib's alias will not be changed in the project's alias and also you won't be warned in cases such parsing from or to Json.
A better approach will be using a data class. For instance:
data class UserName(val userName: String)
And that class will be defined in both lib and project.
Related
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)
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.
Using kotlin plugin 1.3.10 in Android Studio,
when I try to stringify a simple class' object to JSON, it wont compile:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
#Serializable data class Data(val a: Int, val b: Int)
val data = Data(1, 2)
val x = JSON.stringify(data)
However, giving a serialiser works:
val x = JSON.stringify(Data.serializer(), data)
I can't see anybody else having this problem, any idea what the problem is? I've set up using serialisation in gradle.build.
I import with:
import kotlinx.serialization.*
import kotlinx.serialization.json.JSON
The overload of StringFormat.stringify which doesn't take in a serializer (SerializationStrategy) is still experimental. If you view its definition (e.g. ctrl+click on it in the IDE) you'll see it looks as follows:
#ImplicitReflectionSerializer
inline fun <reified T : Any> StringFormat.stringify(obj: T): String = stringify(context.getOrDefault(T::class), obj)
Where that ImplicitReflectionSerializer annotation is itself declared in that same file (SerialImplicits.kt):
#Experimental
annotation class ImplicitReflectionSerializer
So because it's still experimental, you need to do exactly as the warning says, i.e. tell the compiler to allow the use of experimental features, by adding an annotation such as #UseExperimental... where you're using it.
Note that the quick example shown on the kotlinx.serialization GitHub repo's main readme shows that you need to pass in a serializer when calling stringify.
I have simple data classes. I mean, they are data classes logically, but not data class, because I need inheritance and other constructors. They only have fields (of basic types Int?, String?, or List<String>?, etc), and constructors.
I need to pass them (all of their fields need to be passed) from Activity to Activity, so I need to make them parcellisable (or is there a better way?). I first created them as data class and just added #Parcelize. Even though there was a warning and red line that said "CREATOR" or something, I could ignore them and the app worked as intended.
But, now for the reasons above, I changed them to normal classes, and suddenly there is a compilation error.
Error:java.util.NoSuchElementException: Collection contains no element matching the predicate.
at org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension.getPropertiesToSerialize(ParcelableCodegenExtension.kt:374)
....too long...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Error:Execution failed for task ':app:kaptGenerateStubsDebugKotlin'. Internal compiler error. See log for more details
How can I solve this problem? Should I use data class? But I need to parse a JSON object to create them. The classes look like these (not actual classes, but simplified for illustration purposes). Is there a better way than implementing that boring, bulky parcellable code by hand?
#Parcelize
open class Dog():Parcelable
{
var someField1;
var someField2;
constructor(data:JSON):this()
{
//parse data to set the fields.
}
}
#Parcelize
class Doge:Dog
{
var someField3;
var someField4;
constructor():super(); //I do not use it, but for parcellable
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
PS. I had to switch to PaperParcel. It was very similar to Kotlin's, but it did not require a primary constructor. It only required the same thing to be any constructor, so I could just create a secondary constructor with the same argument names as those of fields, and it worked. Although, I wonder why the CREATOR could not be created automatically.
For example,
#PaperParcel
class Doge:Dog
{
var someField3;
var someField4;
//Needed only for PaperParcel
constructor(someField3, someField4)
{
this.someField3 = someField3;
this.someField4 = someField4;
}
companion object
{
#JvmField val CREATOR = PaperParcelDoge.CREATOR
}
//end of PaperParcel stuff.
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
As stated here, your properties should be declared inside your primary constructor.
Parcelable support
Android Extensions plugin now includes an automatic
Parcelable implementation generator. Declare the serialized properties
in a primary constructor and add a #Parcelize annotation, and
writeToParcel()/createFromParcel() methods will be created
automatically:
#Parcelize
class User(val firstName: String, val lastName: String) : Parcelable
I'm using Kotlin objects to work with my Firebase Database models, as described in the guide. I have many fields that are stored as strings, but really are enums, so to be type-safe I have enum fields in the models, plus a string delegated property that returns the firebase stored value (as suggested in a question I asked some time ago). Now, these fields work if I get/set the string delegate in code, but firebase libs seem to skip them when converting to/from database's json format.
A simple example:
abstract class BaseModel {
#Exclude
open var path: String? = null // fails even if I delete this field!
}
class Weight() : BaseModel() {
constructor(v: Double, u: WeightUnit) : this() {
value = v
unitEnum = u
}
var value: Double = 0.0
#Exclude
var unitEnum: WeightUnit = WeightUnit.KG
var unit: String by EnumStringLowercaseConverter(WeightUnit::class.java).getDelegate(Weight::unitEnum)
}
[...]
val testWeight = Weight(7.0, "kg")
db.getReference("/valid/path/to/save/testWeight").setValue(testWeight)
.addOnSuccessListener { r -> Log.d(LOG_TAG, "set successful") }
.addOnFailureListener { e -> Log.e(LOG_TAG, "set error", e) }
The setValue always gives a Permission Denied error, but works, if I delete unitEnum field and make unit a normal String property.
It's similar for reading: Firebase gives no errors when getting a Weight object, but the weightUnit field is never set to anything else than the default. But, if I manually do weight.unit = "lb", the unitEnum field properly returns WeightUnit.LB.
I'm using firebase libs v10.0.1
Now, the questions:
What can I do to make the delegated properties work correctly with firebase? I can try a different approach to the delegated enum fields, as long as the points from my original question are satisfied (readable, concise and type-safe code).
is there any way to see how exactly do firebase libs convert objects to/from json? Or at least see the converted json? Maybe then I could tweak things myself. Unfortunately, everything firebase-related shows as /* compiled code */ in AndroidStudio.
UPDATE: I could of course add a toMap() method to each model, where I would construct a map containing all the properties needed in firebase, but it would be tiresome to do this for every model, and it solves the saving issue only, the enum fields still wouldn't be set when getting.
The delegated props are also skipped when serializing with GSON. So maybe is there a generic way to make the delegated properties look like regular fields?
Try this code, it should work.
#get:Exclude #set:Exclude
var unitEnum: WeightUnit = WeightUnit.KG
var unit: String
get() = unitEnum.name
set(v) { unitEnum = WeightUnit.valueOf(v) }