When starting to build my first code generation annotation, I've found I can't generate Android classes, such as SharedPreferences, since I start with a Java Library module in order to extend AbstractProcessor.
I'm using kotlinpoet to generate my class, but need to create a property that is of type SharedPreferences.Editor which doesn't seem to be supported. I'm trying to something like the following:
val editorProperty = PropertySpec.builder("editor", android.content.SharedPreferences.Editor)
but this fails since the android package is not available. Does anyone know a workaround for this or is it just not possible?
You can simply use
PropertySpec.builder("editor",ClassName("android.content", "SharedPreferences.Editor"))
as kotlin poet doc says - Type names are dumb identifiers only and do not model the values they name.
Related
If I write in Android Studio in a kotlin file getPackageManager this is automatically changed to "packageManager" in cursive, why does this happen and why should somebody think that this is straightforward to understand?
If I write in Android Studio in a kotlin file getPackageManager this is automatically changed to "packageManager" in cursive, why does this happen
getPackageManager() is a method written in Java. By convention, a method starting with get in Java is considered a field accessor. In Kotlin fields are accessed through properties. When inter-opting with Java, Kotlin automatically converts the Java way of accessing properties with the Kotlin way. This makes your code consistently "Kotliny" even if you're accessing Java classes.
Why should somebody think that this is straightforward to understand?
Because - like the syntax in the Kotlin language itself - once you know how it works, it's straightforward to understand. This goes for most things one learns. Why would someone think this is not straightforward to understand?
So, it means you could understand the cursive stuff like an alias? because normally what you write in a file is something that exists, if you write getPackageManager this exists somewhere, if you write the name of a variable this exists somewhere, but in this case packageManager doesn't really exist
Well, it does exist because the compiler makes it exist, otherwise it wouldn't compile, would it? It's just syntactic sugar. You see packageManager (so that - again - your code looks more like Kotlin). Meanwhile the compiler sees getPackageManager(). Either way it refers to the same thing.
Hope that helps!
By default all the variables are private and their getter and setter are generated by the compilers, when you pick some value it is changed to getter or when you assign value it is changed to setter call by compiler.
class Obj(var variable = "Default Value")
val obj = Obj()
obj.variable // same as obj.getVariable()
obj.variable = "Hello" // same as obj.setVariable("Hello")
Reference: https://kotlinlang.org/docs/reference/java-interop.html#getters-and-setters
I am investigating custom annotation processors for Android applications.
I have a use case where I would like to be able to use an annotation processor to read/amend the AndroidManifest.xml and add the following intent section for all Activities mentioned there in?
<intent-filter>
<action android:name="com.my.package.name.my_activity.open"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Is this possible?
It is a very interesting question but I don't think you could achieve such a task with an annotation processor since those work at Kotlin/Java code generation level.
How would you annotate XML #Something and have it still be valid XML?
Take a look at this:
KoltinConf18 - Annotation processing in Kotlin
At 7:18 Zack goes over annotation processing in Java and it basically says:
Happens at compile time
And you cannot modify code, just generate more
So by using barebones annotation processing you can't really modify the already existing AndroidManifest.xml.
An alternative would be writing a Gradle plugin that generates those bits of XML and merges it with the current XML file that already exists within the project.
Something from the top of my head could be:
Create an annotation and mark all activities that you want to introduce that bit of code
On the plugin side; when you are writing the Gradle task; you may use reflection and figure out which classes are annotated by such extension. Or just make the programmer put those activities in a specific directory inside the source folder, which would be way easier
With the fully qualified class names, you may look at the <activity> nodes in the AndroidManifest.xml, filter out the class names that don't match the list of annotated class names
Modify those nodes with the piece of code you would like to inject.
To get started on how to write a Gradle plugin take a look here
A simple example to get you started could be:
Step 1
You create a separate module to write your plugin if it gets too cumbersome but for this simple example I decided to stick it right in the build.gradle.kts. It doesn't need to be a kotlin Gradle file, but I am more proficient in Kotlin than in Groovy :)
As you can see I have created a text testFile.txt in the root of the project.
In code I just navigate to it and read it; print it's content and then modify it.
You could do the very same thing with your AndroidManifes.xml. Then you would need to recursively iterate over the source files from your srcDir looking for all of those activities annotated by your special annotation and store all of the fully qualified class names inside a List<String>. Then do the necessary replacements inside the AndroidManifest
Note that with this basic configuration the Gradle task appears in the Gradle tab inside the others category, to change that is a little bit off of the scope of annotation processing.
Step 2, profit
It works, as you can see the file has been updated and the println statements show the previous content of the file before modifying it
You could have a template AndroidManifest_template.xml then using a gradle task go through the AndroidManifest_template.xml and generate the real AndroidManifest.xml which would be used to build the app.
In other words, AndroidManest.xml would be a transient part of the build and you could use any XML preprocessor you want to take the template and turn it into the real file.
In case you want to add these intents conditionally depending on flavour of your app, you could use gradle flavours and manifest merging to achieve this - read more about flavours at https://developer.android.com/studio/build/build-variants
Also refer to following question for example of using gradle to modify manifest
https://stackoverflow.com/a/22759572/9640177
I'm trying to bind an android SDK for voice chat (zoom sdk).
They have two .aar files (zoomcoomonlib.aar and zoomsdk.aar)
I know I have to create separate binding project for each .aar and then reference them.
While binding zoomsdk.aar I'm getting the below error
The type `Com.Zipow.Videobox.Onedrive.ErrorEventArgs' already contains a definition for `P0' (CS0102) (B14)
In the .aar file I navigated to the package com.zipow.videobox.onedrive; to the interface IODFoldLoaderListener
And below are the contents of it
So it seems parameter String var1 of method onError is causing the issue.
And xamarin studio generated obj/debug/api.xml confirms it (below screenshot) that onError will have first parameter named p0:
So in this scenario I change the metadata.xml to give this parameter a meaningful name.
Like below screenshot:
But even after doing that I am getting same error. That error didn't resolve.
Moreover now if I see the obj/debug/api/.xml file I see the contents for the class IODFoldLoaderListener remains the same.
So changing the metadata.xml has no effect it seems.
Your definition needs to be changed quite a bit. Here is an example that solves the same problem:
<attr path="/api/package[#name='com.emarsys.mobileengage.inbox']/interface[#name='ResetBadgeCountResultListener']/method[#name='onError' and count(parameter)=1 and parameter[1][#type='java.lang.Exception']]" name="argsType">ResetBadgeCountResultListenerOnErrorArgs</attr>
Please note the /interface and argsType items here as your initial definition is incorrect. You would then change the parameters to strings instead of java.lang.Exception from my example.
I'm building a Xamarin android app.
After xamarin update I get a build error. I can't tell if that is related at all but what worked before now throws an exception: ClassNotFoundException. The class exists and is a proper activity. There was no change in that area at all.
I've seen many SO threads in that regard but they all suggest to clean solution, delete build folder etc. And that doesn't help me unfortunately.
What I realise is that in the exception message
W/monodroid( 4201): JNIEnv.FindClass(Type) caught unexpected exception: Java.Lang.ClassNotFoundException: md56629fa8edd4a41a77563f74c5f9a5f792.MainActivity the md5 part is not the same as the folder where the MainActivity.class resides in (md56b5cfc81a7b5c4227a0c9a4dcb7dab856). When I delete that folder a new one is generated during a build. Its md5 is different again. But the exception reappears, asking for the same old md5 as before.
My questions:
1.) What can I do to make JNIEnv look for the right md5?
2.) How are those md5 generated and why
Thanks.
Xamarin generates an MD5 sum for all types that have a Android Callable Wrapper (ACW), unless you explicitly give it a name. I guess this is done to avoid collisions with other types, which could live in the same package name. These are generated at build time.
In order to set your own name on a type inheriting from Java.Lang.Object, these are types like Activity, Adapter, View and many more. You simply add a Register attribute to your class:
[Register("my.cool.package.MyTypeName")]
public class MyTypeName : SomeJavaType
{
}
For classes such as Activity you can alternatively use the Name property in the Activity attribute:
[Activity(Label = "MyActivity", Name = "my.cool.package.MyActivity")]
public class MyActivity : Activity
{
}
I look at Android source code
https://android.googlesource.com/platform/libcore/+/cff1616/luni/src/main/java/java/util/Arrays.java#1742
I realize Android's Arrays contains a public static method named checkStartAndEnd, which is not found in Java standard SE.
However, when I type java.util.Arrays.checkStartAndEnd in Android Studio, or look at the documentation https://developer.android.com/reference/java/util/Arrays.html, I realize checkStartAndEnd isn't valid for Android's Arrays class.
May I know why is it so? Am I looking at wrong Android source code?
You cannot see / use it because it's hidden (check the #hide tag in the Javadoc). If you compare the Android Arrays class with the Java SE one, you'll see that this checkStartAndEnd basically equals to rangeCheck, which is a private method there as well. As to why did they rename it? I have no idea, maybe some licensing issue or something else.