Why Kotlin throws an exception in a very simple piece of code? - android

There are an update in the end of the question. Now I've got to turn on and turn off the error with one line change in a tiny project with all code transcribed below.
The answer of Alexey Romanov is brilliant. Check in the end of the question. it's an amazing and unknown feature of Android Studio environment.
I've found a very kinky error in Kotlin using updated Android 3.4.2.
First I've run my test code in my computer (not Android) using main modules in any kotlin file in my only module. It always works for me, but it has started to give an error that I comment below and then another error that don't allow to run the main module anymore.
Searching in Stack Overflow, one user as claimed that the last described error ends when one uses test files under java folder (Not Android test files)
However is has continued to give the first error, which I commented above, which I managed to reduce to the following schematic example:
class Cl(
var a:Int=0
)
var vCl = arrayListOf<Cl>()
And the main module is:
fun main(){
println("start")
vCl.clear() // error points to this line
println("ok")
}
I just point to fun main() line in test file and click the green icon.
The error message
Exception in thread "main" java.lang.ExceptionInInitializerError
Suddenly the error stops when I've changed one global statement from one file to another file (in the top part, outside any class or function).
var timings = TimingLogger("MyTag", "Your")
It's crazy. I'm freaking out!
Update:
=======
I've made newerror, a new tiny project with one module to reproduce the error decribed in this question. Below is the complete code:
Gradle: No change after project creation.
AndroidManifest.xml: No change after project creation.
activity_main.xml: It is bare bone because all my views are dinamically created:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/myLayout"
android:textAllCaps="false"
android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=
"br.com.greatsolutions.paulo.myerror.MainActivity">
</RelativeLayout>
The MainActivity.kt code:
package br.com.greatsolutions.paulo.myerror
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
The compiler.kt code
package br.com.greatsolutions.paulo.myerror
import android.util.TimingLogger
var timings = TimingLogger("MyTag", "Your")
class Cl(
var a:Int=0
)
var vCl = arrayListOf<Cl>()
Finally the test.kt code:
package br.com.greatsolutions.paulo.myerror
fun main(){
println("start")
vCl.clear()
println("ok")
}
The complete error message when I run test.kt code in my computer (not in Android):
start
Exception in thread "main" java.lang.ExceptionInInitializerError
at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt:5)
at br.com.greatsolutions.paulo.myerror.TestKt.main(test.kt)
Caused by: java.lang.RuntimeException: Stub!
at android.util.TimingLogger.<init>(TimingLogger.java:59)
at br.com.greatsolutions.paulo.myerror
.CompilerKt.<clinit>(compiler.kt:5)
... 2 more
And, again, if I put the below declaration inside MainActivity.kt and run the test.kt again in my computer ...
var timings = TimingLogger("MyTag", "Your")
... and the error is gone!
start
ok
For those who want to see to believe:
Conclusion: Now I know how to avoid this crazy error, but I don't understand why it works! Ih theory, the source file that I use to make my declarations should be interchangeable, because all files are in the same module!
Solution of puzzle
Alexey Romanov has hit the nail on the head!
I've researched a little bit more and I've found that Android Studio only executes the declarations in a file if any variable of the scope was used in a computer test.
When
var timings = TimingLogger("MyTag", "Your")
is inside MainActivity.kt, no error is shown.
After I put in this file the following code:
open class Fool(val a:Int=5){
init { println("fool") }
}
class SuperFool(a:Int=8):Fool(a) {
init { println("what a big fool") }
}
var v = SuperFool()
When I put println(v.a) my code in test.kt becomes:
package br.com.greatsolutions.paulo.myerror
fun main(){
println("start")
println(v.a) // new line
vCl.clear()
println("ok")
}
And it gives the same error than before! Take out this line and no error again!
The solution is if one has any declaration of some variable in an Android class in your projet you MUST use lateinit and just initializate in other point of code that will not run in test execution.
In this case one can do
lateinit var timings:TimingLogger
And put the initialization in other place (inside onCreate in MainActivity class, for instance. In my case, immediately before the 1st. call addSplit, one of the methods from TimingLogger class
timings = TimingLogger("MyTag", "Your")
Now my test code gently prints
start
fool
what a big fool
ok

The problem has nothing to do with Kotlin; you can't just use Android classes in code running directly on your computer and not in an emulator, because the versions in the standard jar will crash on first use. They are just there to provide class files with the same names and method signatures as will exist on the device, so that your code can compile (not run!).
Use Robolectric to get a usable-for-JVM-tests version of the Android library.
if I put the below declaration inside MainActivity.kt and run the test.kt again in my computer
Then your test doesn't use any Android classes (see below for details).
It is worth remembering that, within the same module, all declarations, regardless of what file they are in, are executed.
That's wrong. What happens with top-level val/var/fun declarations in Kotlin is that they get wrapped into a single class for each file, so your code works like this:
// compiler.kt
package br.com.greatsolutions.paulo.myerror
object CompilerKt { // you can actually see the name in the stack trace
var timings = TimingLogger("MyTag", "Your")
val vCl = arrayListOf<Cl>()
}
class Cl(
var a:Int=0
)
// test.kt
package br.com.greatsolutions.paulo.myerror
object TestKt {
fun main(){
println("start")
CompilerKt.vCl.clear()
println("ok")
}
}
So calling TestKt.main() forces loading and initialization of CompilerKt because you reference vCl there. This includes calling the constructor TimingLogger("MyTag", "Your") which thrown an exception when the stub library is used.
Calling TestKt.main() does not load MainActivity (which you can confirm by adding some print to that file), so if you move var timings there, nothing in the stub library gets called.

Your missing a an quote on your first line.

Related

SonarLint: Suppress specific line warning in Android Studio

There is a special case in our code where we need to call setText with getText of the same TextView. SonarLint warn this with kotlin:S1656, to suppress it we use #SuppressWarnings annotation but it does not work. It started showing an error to the IDE and the warning from SonarLint still there. //NOSONAR is not working as well
No action is available as well for suppressing it.
Can you put the SuppressWarnings annotation before your method?
// Sonarlint plugin version 6.8.0.50884
#SuppressWarnings("kotlin:S1656")
fun yourMethod() {
// ...
withContext(coroutineDispatcherMain) {
// Set the text again to fix images overlapping text
htmlTextView.text = htmlTextView.text
}
}
I also wonder that if htmlTextView.invalidate() would work for you.
UPDATE:
SuppressWarnings annotation is also available for LOCAL_VARIABLE but I think the problem is more complicated in kotlin code.
If you examine the byte code of these lines below, you can see that some.num = ... is some.setNum(...) in byte code.
Sonar plugin's code analyser may not handle this kind of kotlin specific codes.
class Sonar {
private val some = Something(0)
private var member = 0
#Deprecated("...")
fun myNumber(): Int = 1
fun doThat() {
// local variable working
#SuppressWarnings("kotlin:S1874") // Code annotated as deprecated should not be used
val localNum = myNumber()
// not working
#SuppressWarnings("kotlin:S1874")
some.num = myNumber()
// not working
#SuppressWarnings("kotlin:S1874")
member = myNumber()
// ...
}
}
I found the docs for the issue to confirm its the right number: https://rules.sonarsource.com/kotlin/RSPEC-1656
Also read the source at https://github.com/SonarSource/sonarlint-intellij but not much help.
It seems like "// NOSONAR" is what you want (I know you said you tried it, but did you have the space? And have it at the end of the line?)
htmlTextView.text = htmlTextView.text // NOSONAR
Just a guess here, but you could also try:
#SuppressWarnings("RSPEC:S1656")
lastly to check suppress is working you can ignore everything:
#SuppressWarnings("all")
htmlTextView.text = htmlTextView.text
Big discussion here: Turning Sonar off for certain code
This article also gives you options to choose from: https://docs.codescan.io/hc/en-us/articles/360012109711-Ignoring-violations

Printing Compose Semantics Tree to the Log

I am following Testing in Jetpack Compose Codelab but I am unable to print the Semantics tree using the printToLog function on a node. I am using this code inside the androidTest package.
import androidx.compose.ui.test.printToLog
...
//testing function
#Test
fun rallyTopAppBarTest() {
val allScreen = RallyScreen.values().toList()
testRule.setContent {
//the component be tested in isolation goes here
RallyTopAppBar(
allScreens = allScreen,
onTabSelected = {},
currentScreen = RallyScreen.Accounts
)
}
testRule.onRoot().printToLog("currentLabelExists")
....
}
I have checked my LogCat with both Debug and Verbose filtering using "currentLabelExists" tag but the Semantics Tree is not printing on the log.
Any ideas on what I might be missing out.
I can see the semantics tree.
Check if Android Studio Logcat has "No Filters"
Edit
It seems to be a bug as I can sometimes see the logs even if I select "Show only selected application".
Since I just ran into the same problem:
The SemanticsTree seems to appear only when you run your tests in Debug Mode

Trouble instantiating gdx-pay purchaseManager in my game

I am trying to set up gdx-pay in my game. I have followed the official readme (https://github.com/libgdx/gdx-pay/), but can't seem to instantiate the PurchaseManager in my AndroidLauncher.
I have set up a class that handles PurchaseManager installation, but every time I try to instantiate it in the AndroidLauncher I get this error:
"java.lang.RuntimeException: Unable to start activity ComponentInfo{appPackage.AndroidLauncher}: java.lang.IllegalArgumentException: Support for pending purchases must be enabled. Enable this by calling 'enablePendingPurchases()' on BillingClientBuilder."
The readme says to add this to the AndroidLauncher onCreate
game.purchaseManager = new PurchaseManagerGoogleBilling(this);
The problem is that I have to add this
ProjectName.purchaseManager = new PurchaseManagerGoogleBilling(this);
This give me an error saying that I have to make purchaseManager static in my ProjectName class
When I do this, I get the RuntimeException mentioned above.
The sample gdx-pay project instantiates PurchaseManager with this code
public class AndroidLauncher extends GenericAndroidLauncher {
#Override
protected void initFlavor(GdxPayApp game) {
super.initFlavor(game);
game.purchaseManager = new PurchaseManagerGoogleBilling(this);
}
}
When I try this, #Override is invalid and initFlavor does not exist. I tried looking for initFlavor in the gdx-pay files, but had no luck finding anything...
Thanks for taking the time to help
Well it turns out that the following line in my android gradle was the issue
implementation 'com.android.billingclient:billing:2.0.1'
Gdx-pay takes care of this automatically and uses client 1.1
Setting the billingclient to 2.0.1 was the problem. I was able to fix it by deleting that line. Big thanks to the libGdx discord guys!

Can't use application's class in brida

I have recently started using frida and came across this wonderful tool brida, it bridges BURP and Frida.
Now, the problem is if I try to use java.lang.String, it works fine, however if I try to load a class from the android application itself, it gives error like this :
**Exception with custom context application**
net.razorvine.pyro.PyroException: [frida.core.RPCException] java.lang.ClassNotFoundException: Didn't find class "com.package.name.ClassName" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]
The code is like this :
contextcustom2: function(message) {
if(Java.available){
var hexDecodedInput = hexToString(message);
var payloadRequestClass = Java.use("com.package.name.ClassName");
var encryptedPayload = payloadRequestClass.buildPayload(hexDecodedInput);
return stringToHex(encryptedPayload);
}
},
It is for adding functionality to BRIDA context menu option in BURP.
Thanks.
It's likely because that class isn't imported. Frida hooks the app, but it can't hook a class that isn't already there. Either find a way to do what you're trying to do with the imported classes ( note enumerateClasses for this matter ), or you can supply this to the script from the outside ( send() ). Note the docs.

Porting a Java SDK to Xamarin.Android, binding Library error regarding types

I am working on porting a Java SDK to Xamarin.Android.
After solving numerous errors, I am stuck in this one:
/Users/alejandro_l/Projects/KontaktBeaconSDK/KontaktBeaconSDK/obj/Debug/generated/src/Com.Kontakt.Sdk.Android.Ble.Configuration.Scan.EddystoneScanContext.cs(65,65): Error CS1715: Com.Kontakt.Sdk.Android.Ble.Configuration.Scan.EddystoneScanContext.EventTypes': type must beSystem.Collections.Generic.ICollection' to match overridden member `Com.Kontakt.Sdk.Android.Ble.Configuration.Scan.AbstractProfileSpecificScanContext.EventTypes' (CS1715) (KontaktBeaconSDK)
For solving the error I though that it should be OK to use the following line in the metadata.xml telling the generator to change the type of the method to match with the abstract class:
<attr path="/api/package[#name='com.kontakt.sdk.android.ble.configuration.scan']/class[#name='EddystoneScanContext']/method[#name='getEventTypes' and count(parameter)=0]" name="managedType">System.Collections.Generic.ICollection</attr>
Based on: https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/customizing-bindings/java-bindings-metadata/#managedType
The generated output still has the wrong shape:
static IntPtr id_getEventTypes;
public override unsafe global::System.Collections.ICollection EventTypes {
// Metadata.xml XPath method reference: path="/api/package[#name='com.kontakt.sdk.android.ble.configuration.scan']/class[#name='EddystoneScanContext']/method[#name='getEventTypes' and count(parameter)=0]"
[Register ("getEventTypes", "()Ljava/util/Collection;", "GetGetEventTypesHandler")]
get {
if (id_getEventTypes == IntPtr.Zero)
id_getEventTypes = JNIEnv.GetMethodID (class_ref, "getEventTypes", "()Ljava/util/Collection;");
try {
return global::Android.Runtime.JavaCollection.FromJniHandle (JNIEnv.CallObjectMethod (Handle, id_getEventTypes), JniHandleOwnership.TransferLocalRef);
} finally {
}
}
}
As you can see System.Collections.ICollection is generated and not System.Collections.Generic.ICollection.
Note: I even still did not figure it out how to also set the generic type, but that is the next step.
I am a newbie in binding and I cannot figure it out. Can someone tell me what I am doing wrong? Am I trying an impossible here? Do I need to change the source code of the SDK? (Notice that if I use "Build" and change manually the output I may get the dll in a future (still 49 errors left though) but I wonder if I can solve in a proper way).
Another useful link I used: https://gist.github.com/brendanzagaeski/9607158
Found it:
<attr path="/api/package[#name='com.kontakt.sdk.android.ble.configuration.scan']/class[#name='EddystoneScanContext']/method[#name='getEventTypes' and count(parameter)=0]" name="return">System.Collections.Generic.ICollection<Com.Kontakt.Sdk.Android.Ble.Discovery.EventType></attr>
It seems the error has disappeared due to name="return". I do not know why return does not appear in the documentations but it appears in the api.xml so it should be OK.

Categories

Resources