KMM: Cast string to NSString get warning "This case can never succeed" - objective-c

In my KMM project, I have a method to invoke Objective-C predicateWithFormat method inside it. And when I try to cast the passing string text to NSString, I got this warning "This cast can never succeed".
override fun elementWithPredicateText(text: String): AppElement {
val predicate = NSPredicate.predicateWithFormat("label CONTAINS %#", text as NSString)
val query = app.descendantsMatchingType(XCUIElementTypeAny).matchingPredicate(predicate)
return UIElementWrapper(query)
}
The problem here if I don't cast this String to NSString, it gets a build error as below screenshot showed. For now with type cast from String to NSString, the build could be successful, although it has this annoying warning, so how could I solve this warning?
The iOS method which are used.
#kotlin.commonizer.ObjCCallable public open external expect fun predicateWithFormat(predicateFormat: kotlin.String, vararg args: kotlin.Any?): platform.Foundation.NSPredicate { /* compiled code */ }

I believe warning you get when casting to NSString is a Android Studio or Kotlin compiler bug. It is recommended way to cast Kotlin String to NSString see documentation on depreceted method and Kotlin documentation.
You can suppress it with #Suppress("CAST_NEVER_SUCCEEDS").

Related

Warning: "Safe call on a non-null receiver will have nullable type in future releases" due to nullable variable in buildvariants

I'm developing an android app using Kotlin.
I have two different build-variants - VariantA and VariantB - both containing a configuration-File:
// VariantA
object ConfigEnvironment {
val SOME_PARAMETER: String? = null
}
// VariantB
object ConfigEnvironment {
val SOME_PARAMETER: String = "This is a config"
}
In my code i'm calling:
ConfigEnvironment.SOME_PARAMETER?.let { Log.d("tag", "$it" }
When building VariantB the Compiler throws a warning:
Safe call on a non-null receiver will have nullable type in future releases
While this is correct for this variant, it's somewhat impractical - since i need the nullability in the other variant.
Can i safely supress this lint?
(And how do i do that? My IDE didn't suggest any fixes)
It should be safe to suppress this warning since you do not call something which expects a non-nullable expression for it inside the let.
You can suppress the warnung like this:
#Suppress("UNNECESSARY_SAFE_CALL")
ConfigEnvironment.SOME_PARAMETER?.let { Log.d("tag", "$it") }
IntelliJ can help you with that. Just move the cursor to the ?. and type your shortcut for Quick Fix (you can look it up in the Keyboard settings):

Need to serialize Any to base64 string in Kotlin

I have the following code in Kotlin which I aim to use it to convert any instance to a base64 encoded string. The same is not working and it throws the following error :
Serializer for class 'Any' is not found.\nMark the class as #Serializable or provide the serializer explicitly
How can I fix this?
class SerializerAdapter: SerializerPort {
private val logger: Logger = LoggerFactory.getLogger(javaClass.simpleName)
override fun toBase64(input: Any): String {
try {
val jsonString = Json.encodeToString(input)
return Base64.getEncoder().encodeToString(jsonString.toByteArray())
}catch (ex: Exception) {
logger.error("[BASE64 Error] error converting json object to base64 encoded string: ${ex.stackTraceToString()}")
}finally {
return ""
}
}
}
Serializing just Any is not as simple as it sounds. Serialization framework has to know the type of the data to serialize. It can use either compile type (Any in your case) or runtime type (actual type provided to toBase64()). Both options have their drawbacks. Runtime type is incomplete due to type erasure, so e.g. List<Int> and List<String> are the same. On the other hand, compile-time type may be totally lost, e.g. in generics or in cases like yours.
Kotlin serialization generally prefers compile types, especially because reified parameters make them much more usable. Unfortunately, we can't use reified here, because toBase64() is a virtual function, so it can't be inlined.
My suggestion is to change the signature of this function to additionally receive KType of provided data and then create inline function to make it convenient to use:
override fun toBase64(input: Any, type: KType): String {
try {
val serializer = Json.serializersModule.serializer(type)
val jsonString = Json.encodeToString(serializer, input)
...
}
}
#OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> SerializerPort.toBase64(input: Any) = toBase64(input, typeOf<T>())
Alternatively, we can serialize using the runtime type, but note the problems I mentioned earlier - it may not work well with generics.
val serializer = Json.serializersModule.serializer(input::class.starProjectedType)
val jsonString = Json.encodeToString(serializer, input)
kotlinx.serialization is a compile-time library, so you must know up front every object you want to serialise.
Depending on your use case;
You would either need to use a runtime library that uses reflection (such as one of the many JSON, or XML serialisers) to be able to use classes that are not your own.
Or if your requirement is more that you want to be able to serialise other peoples classes then you can use your own interface that others must implement along with #Serializable (see https://github.com/Kotlin/kotlinx.serialization/issues/1005).

Alias an Annotation that takes an argument

I'm trying to convert my Android library to a Kotlin multiplatform library.
One of the things I want to preserve are all the android specific annotations for Android Lint. I was able to convert most of them by doing simple things like
#MustBeDocumented
#Retention(AnnotationRetention.BINARY)
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CLASS,
AnnotationTarget.VALUE_PARAMETER
)
expect annotation class MainThread()
actual typealias MainThread = androidx.annotation.MainThread
This did not work with RestrictTo because it takes an argument.
The android RestrictTo annotation looks like
#Retention(CLASS)
#Target({ANNOTATION_TYPE,TYPE,METHOD,CONSTRUCTOR,FIELD,PACKAGE})
public #interface RestrictTo {
/**
* The scope to which usage should be restricted.
*/
Scope[] value();
enum Scope {
}
}
I cannot seem to make the compiler happy with the type for value.
If I make the expect look like
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FIELD,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CLASS
)
#MustBeDocumented
#Retention(AnnotationRetention.BINARY)
expect annotation class RestrictTo(vararg val value: RestrictScope)
I get a compile error
public expect final val value: Array<out RestrictScope /* = RestrictTo.Scope */>
The following declaration is incompatible because return type is different:
public final val value: Array<RestrictTo.Scope>
If I change the value from a vararg to an Array I get this error.
public constructor RestrictTo(value: Array<RestrictScope /* = RestrictTo.Scope */>)
The following declaration is incompatible because parameter types are different:
public constructor RestrictTo(vararg value: RestrictTo.Scope)
Is there anyway to make the types work for both the constructor and the values method?
This is a bug - https://youtrack.jetbrains.com/issue/KT-20900
Feel free to upvote the issue.

How to handle nullable generics with Java interop

I have a Java class that is out of my control, defined as:
public #interface ValueSource {
String[] strings() default {}
}
I am trying to use this class from a Kotlin file I control, like so:
class Thing {
#ValueSource(string = ["non-null", null])
fun performAction(value: String?) {
// Do stuff
}
}
I get a compiler error
Kotlin: Type inference failed. Expected type mismatch: inferred type is Array<String?> but Array<String> was expected.
I understand why the inferred type is Array<String?>, but why is the expected type not the same? Why is Kotlin interpreting the Java generic as String! rather than String?? And finally, is there a way to suppress the error?
Kotlin 1.2.61
This isn't a Kotlin issue - this code isn't valid either, because Java simply doesn't allow null values in annotation parameters:
public class Thing {
#ValueSource(strings = {"non-null", null}) // Error: Attribute value must be constant
void performAction(String value) {
// Do stuff
}
}
See this article and this question for more discussion on this.

Typedef Return-Type in Objective-C does not work in Swift

I want to use a Objective-C class in my Swift project and have imported the files and Xcode created the bridge header file and everything is cool... except:
The Objective-C class defines a callback type for a function
typedef void (^SSScanManagerCallback)(BOOL success, NSError *error, NSArray *scannedURLs);
And uses the type in the function declaration
- (void)scanSync:(SSScanManagerCallback)callback; // Synchronous scan.
The class in question is the following: https://github.com/counsyl/scanstream/blob/master/ScanStream/SSScanManager.h#L16
If I then want to use the class in Swift:
let scanManager = SSScanManager();
scanManager.scanSync({(_ success: Bool, _ error: Error, _ scannedURLs: [Any]) -> Void in
if !success {
// ...
}
});
I get the following error:
Cannot convert value of type '(Bool, Error, [Any]) -> Void' to expected argument type 'SSScanManagerCallback!'
Update: Even if I try to set the argument type like so:
scanManager.scanSync({(_ justATry: SSScanManagerCallback!) -> Void in
});
I get the error:
Cannot convert value of type '(SSScanManagerCallback!) -> Void' to expected argument type 'SSScanManagerCallback!'
But how would I set the type to just 'SSScanManagerCallback!' as requested in the error message?
Interestingly, it appears that Swift (tested with 3.0.2) now imports Objective-C block argument types without any nullability annotations as strong optionals (previously they were imported as implicitly unwrapped optionals). I can't seem to find the documentation for this change though.
So in your case, the correct signature is:
scanManager.scanSync {(success: Bool, error: Error?, scannedURLs: [Any]?) -> Void in
// ...
}
But never write it like this, always let Swift infer the argument types where it can, it solves these kinds of type-mismatch problems for you.
scanManager.scanSync { success, error, scannedURLs in
// ...
}
Now you can ⌥ click on the closure arguments and Xcode will tell you the type that Swift infers them to be.