Class.getDeclaredMethods() returns inherited default methods - java

The method getDeclaredMethods, when called on a class object, is supposed to return an array of Method objects representing the methods that are declared directly as part of that class. It's not supposed to return any inherited methods.
This works fine when I install my app directly via Android Studio, regardless of the active build variant. Switching to a release build is not sufficient to trigger the problem.
The problem arises when compiling an APK or App Bundle (.aab) and installing the app that way. (Either directly by copying the APK onto a device, or rolling out the bundle on the Google Play Store and installing the app from there.)
Here's my test scenario, in a fresh Android Studio project, using SDK 33, minSdk 21 (Android 5.0), minifyEnabled false, and the default proguardFiles statement deleted, to make sure this isn't caused by R8 / ProGuard.
The interface:
// TestInterface.java
package com.example.testapp;
public interface TestInterface {
default String methodWithDefault() {
return "default";
}
String methodWithoutDefault();
}
The implementing class:
// TestClass.java
package com.example.testapp;
public class TestClass implements TestInterface {
#Override
public String methodWithoutDefault() {
return "non-default";
}
}
The test case:
// MainActivity.java
package com.example.testapp;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestClass test = new TestClass();
StringBuilder sb = new StringBuilder("Methods:\n");
for (Method m : TestClass.class.getDeclaredMethods()) {
sb.append('\n').append(m.toString()).append('\n');
try {
String s = (String) m.invoke(test);
sb.append("Result: ").append(s).append('\n');
} catch (InvocationTargetException e) {
sb.append("Target exception: ").append(e.getTargetException()).append('\n');
} catch (IllegalAccessException e) {
sb.append("Illegal access.\n");
}
}
System.out.println(sb);
TextView textView = findViewById(R.id.textView);
textView.setText(sb.toString());
}
}
Contents of app/build.gradle:
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.testapp'
compileSdk 33
defaultConfig {
applicationId "com.example.testapp"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility 11
targetCompatibility 11
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
Output when running directly from Android Studio:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Result: non-default
Output when building an APK and installing it on the device:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithDefault()
Result: default
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Result: non-default
Questions:
Why does this happen?
What's the best way to work around it?

In typical rubber-duck debugging fashion, I found out some important details and how to work around this while improving the test-case before posting it on StackOverflow...
First, let's have some properties of the methods printed as well. We can modify MainActivity as follows:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestClass test = new TestClass();
StringBuilder sb = new StringBuilder("Methods:\n");
for (Method m : TestClass.class.getDeclaredMethods()) {
sb.append('\n').append(m.toString()).append('\n');
sb.append("Synthetic: ").append(m.isSynthetic()).append('\n');
sb.append("Bridge: ").append(m.isBridge()).append('\n');
sb.append("Default: ").append(m.isDefault()).append('\n');
try {
String s = (String) m.invoke(test);
sb.append("Result: ").append(s).append('\n');
} catch (InvocationTargetException e) {
sb.append("Target exception: ").append(e.getTargetException()).append('\n');
} catch (IllegalAccessException e) {
sb.append("Illegal access.\n");
}
}
System.out.println(sb);
TextView textView = findViewById(R.id.textView);
textView.setText(sb.toString());
}
}
This makes Android Studio complain, because isDefault() is only available starting from SDK 24, but our minSdk is 21.
Let's increase minSdk to 24 and check the output:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Synthetic: false
Bridge: false
Default: false
Result: non-default
Oh, the inherited method is gone! If you play around with minSdk, you'll realize the issue appears with any value <= 23. So, we make the first important realization:
1. The problem only arises if minSdk is less than 24.
(Note that the actual SDK version of the Android device on which you're installing the APK doesn't seem to matter; I'm testing this all on an SDK 25 / Android 7.1.1 device.)
Let's switch back to minSdk 21, and make the call to isDefault conditional on an SDK version check, like so:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestClass test = new TestClass();
StringBuilder sb = new StringBuilder("Methods:\n");
for (Method m : TestClass.class.getDeclaredMethods()) {
sb.append('\n').append(m.toString()).append('\n');
sb.append("Synthetic: ").append(m.isSynthetic()).append('\n');
sb.append("Bridge: ").append(m.isBridge()).append('\n');
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sb.append("Default: ").append(m.isDefault()).append('\n');
}
try {
String s = (String) m.invoke(test);
sb.append("Result: ").append(s).append('\n');
} catch (InvocationTargetException e) {
sb.append("Target exception: ").append(e.getTargetException()).append('\n');
} catch (IllegalAccessException e) {
sb.append("Illegal access.\n");
}
}
System.out.println(sb);
TextView textView = findViewById(R.id.textView);
textView.setText(sb.toString());
}
}
This yields the following output when installing the new APK on a device with SDK version 24 or newer:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithDefault()
Synthetic: true
Bridge: false
Default: false
Result: default
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Synthetic: false
Bridge: false
Default: false
Result: non-default
Confusingly, the method is not marked as default. But it's marked as synthetic, so:
2. We can filter out the inherited methods via Method.isSynthetic().
So, to answer our questions:
Why does this happen?
I suppose the synthetic method is being generated when minSdk is less than 24 because the APK could be installed on an older Android device which doesn't have "direct" support for default interface methods in its JVM.
When installing the app directly via Android Studio, I guess the minSdk value is ignored, since Android Studio can check what the actual version of the device is.
If anyone has more exact information, please share.
What's the best way to work around it?
The inherited default methods can be filtered out by calling isSynthetic() on them, which will return true.
If you want to keep some other synthetic methods and only filter out these ones, I don't know how to achieve that, but that should be an exceedingly rare situation.

Related

Android - Set TextView background programatically to drawable before API 24

I am trying to set the background of an TextView that acts as a button to an XML vector.
if (condition) {
textViewButton.setBackgroundResource(R.drawable.button_selected);
} else if (condition) {
textViewButton.setBackgroundResource(R.drawable.button_correct);
} else {
textViewButton.setBackgroundResource(R.drawable.button_unselected);
}
I thing this was introduced in API24 and I can't load it in my API22 test phone as I get the following error:
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7f070069
at android.content.res.Resources.getValue(Resources.java:1324)
at android.content.res.Resources.getDrawable(Resources.java:828)
at android.content.Context.getDrawable(Context.java:408)
at android.view.View.setBackgroundResource(View.java:16251)
at androidx.appcompat.widget.AppCompatTextView.setBackgroundResource(AppCompatTextView.java:113)<
Is there any way to acomplish this changes to imageView design programaticaly for older API's?(works just fine on API 29 and 30)
This is the recommended way
textViewButton.setBackground(ContextCompat.getDrawable(context,R.drawable.button_selected));
If it doesn't work in your app's build.gradle you need to include:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
And add the following to onCreate:
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);

incompatible types: MainActivity cannot be converted to LifecycleOwner

MainActivity cannot be converted to LifecycleOwner
I used this as LiveCycle Owner, but it is rejected and I got an error as you see in the picture.
I work on Api 25 and I this the problem may be related to this version
This is info about my sdk
compileSdkVersion 25
buildToolsVersion '25.0.2'
This is my code:
private void retrieveTasks() {
Log.d(TAG, "Actively retrieving the tasks from the DataBase");
// Extract all this logic outside the Executor and remove the Executor
// Fix compile issue by wrapping the return type with LiveData
LiveData<List<TaskEntry>> tasks = mDb.taskDao().loadAllTasks();
// Observe tasks and move the logic from runOnUiThread to onChanged
tasks.observe(this, new Observer<List<TaskEntry>>() {
#Override
public void onChanged(#Nullable List<TaskEntry> taskEntries) {
Log.d(TAG, "Receiving database update from LiveData");
mAdapter.setTasks(taskEntries);
}
});
}
I put LiveData dependencies in my Gradle
compile "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
If anyone knows the reason for the problem, let me know please
Fragments and Activities in Support Library 26.1.0 and later already implement the LifecycleOwner interface by default
but in version 25 you need to implement LifecycleOwner interface
for example
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
#Override
public void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}
#NonNull
#Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
Source : Handling lifecycles with lifecycle-aware components
As you can read here the LifecycleOwner was added in support library 26.1.0. Easiest way to fix your issue would be upgrading your support library version.
Had the same error. Upgrading to androidx support libraries fixed the issue.
Choose inside Android Studio: Refactor -> Migrate to android x

I made an app which crashes and I can't see any reason for that

I recently made an app with Android Studio in which I integrated the Dialogflow API.
My code is error free, but, the app is crashing as I run it on the emulator.
I'm running :
Android Studio 3.1.3
Gradle Version : 4.4
Android Plugin Version : 3.1.3
Emulator : Nexus 5X API-26
My Project Level Gradle File :
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
**My App's Gradle File :**
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.therationalbloke.myzen"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'ai.api:sdk:2.0.7#aar'
implementation 'ai.api:libai:1.6.12'
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'commons-io:commons-io:2.4'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
My MainActivity.java Code :
package com.testfile.dialogf;
import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import ai.api.AIListener;
import ai.api.android.AIConfiguration;
import ai.api.android.AIService;
import ai.api.model.AIError;
import ai.api.model.AIResponse;
import ai.api.model.Result;
public class MainActivity extends AppCompatActivity implements AIListener {
AIService aiService;
TextView t;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
t = (TextView) findViewById(R.id.textView);
int permission = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO);
if (permission != PackageManager.PERMISSION_GRANTED) {
makeRequest();
}
final AIConfiguration config = new AIConfiguration("CLIENT_ACCESS_TOKEN",
AIConfiguration.SupportedLanguages.English,
AIConfiguration.RecognitionEngine.System);
AIService aiService = AIService.getService(this, config);
aiService.setListener(this);
}
protected void makeRequest() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
101);
}
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case 101: {
if (grantResults.length == 0
|| grantResults[0] !=
PackageManager.PERMISSION_GRANTED) {
} else {
}
return;
}
}
}
public void buttonClicked (View view){
aiService.startListening();
}
#Override
public void onResult(AIResponse result) {
Log.d("ZenR", result.toString());
Result result1 = result.getResult();
t.setText("Query "+result1.getResolvedQuery()+" action: "+result1.getAction());
}
#Override
public void onError(AIError error) {
}
#Override
public void onAudioLevel(float level) {
}
#Override
public void onListeningStarted() {
}
#Override
public void onListeningCanceled() {
}
#Override
public void onListeningFinished() {
}
}
Now, when I'm running this code on my emulator. The app is crashing. There are no errors in the syntax whatsoever. I'm not really sure what this is about.
Here's the error on my emulator as app crashes
The reason for an app to crash can be Compile-Time Errors or Run-Time Errors.
Compile-Time Errors can be syntax errors , class not found errors, overloading errors which are shown and recorded by the Android Studio. In your case there would be no Compile time errors since your app is correctly getting compiled and installed on the device.
Run- Time errors occur when you are using (running) the app. They are mostly logical errors or variable declaration errors. In your case may be the problem lies here only.
When you are running the app the codes are being executed and the logic is also executed. Thus if you have given some wrong logic in your code such as infinite loop, passing string instead of integer data type and many more then the app is getting crashed.
You must check the Log to see what is the exception which caused the crash. In the log (lower part of the IDE) there will be the name of the Exception, the element which launched it and also there will be the links to the lines of the code where the problem was found.
If you still can't find the error this way, you can set breakpoints at the left of the lines of code where you want the application stops during debug, and then run the app in the debug mode to see exactly what happens.
Another tip: often, when I was sure about the rightness of my code and it runs but crashes, it's about having declared all the Activities in the manifest (or all the Services) and to have set all required permission. Because in this case there are no apparent errors before running the app.
You've already declared aiService globally:
AIService aiService;
No need to declare inside onCreate() again. Replace the following line:
AIService aiService = AIService.getService(this, config);
With:
aiService = AIService.getService(this, config);

RoboSpice throwing okhttp exceptions

I use RoboSpice-Retrofit for calling my server REST api which has been working without problems until a few days ago when every single call now throws an exception, Example:
D/Retrofit: java.lang.NoSuchMethodError: No direct method <init>(Lcom/squareup/okhttp/OkHttpClient;Lcom/squareup/okhttp/Request;ZZZLcom/squareup/okhttp/Connection;Lcom/squareup/okhttp/internal/http/RouteSelector;Lcom/squareup/okhttp/internal/http/RetryableSink;Lcom/squareup/okhttp/Response;)V in class Lcom/squareup/okhttp/internal/http/HttpEngine; or its super classes (declaration of 'com.squareup.okhttp.internal.http.HttpEngine' appears in /data/app/com.company.app.customerapp-1/base.apk)
at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.newHttpEngine(HttpURLConnectionImpl.java:362)
at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.initHttpEngine(HttpURLConnectionImpl.java:312)
at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:377)
at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:73)
at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:38)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.getTest(Unknown Source)
at com.adoperator.tidyapp.TestActivity$TestRequest.loadDataFromNetwork(TestActivity.java:67)
at com.adoperator.tidyapp.TestActivity$TestRequest.loadDataFromNetwork(TestActivity.java:54)
at com.octo.android.robospice.request.CachedSpiceRequest.loadDataFromNetwork(CachedSpiceRequest.java:48)
at com.octo.android.robospice.request.DefaultRequestRunner.processRequest(DefaultRequestRunner.java:150)
at com.octo.android.robospice.request.DefaultRequestRunner$1.run(DefaultRequestRunner.java:217)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
D/Retrofit: ---- END ERROR
dependencies:
compile 'com.octo.android.robospice:robospice:1.4.14'
compile 'com.octo.android.robospice:robospice-cache:1.4.14'
compile 'com.octo.android.robospice:robospice-retrofit:1.4.14'
I suspect based on the exception that there is something wrong with the compiler, but I just tested on another computer with a fresh install of Java and Android Studio on the same project but same problems still...
This error is driving me crazy...
Anyone knows anything that could be of help? Any help is highly appreciated.
EDIT
MainActivity.java:
SpiceManager spiceManager = new SpiceManager(TestAPIService.class);
protected void onStart() {
super.onStart();
spiceManager.start(this);
spiceManager.execute(new TestRequest(), new RequestListener<ResponseData>() {
...
});
}
TestAPIService.java:
public class TestAPIService extends RetrofitGsonSpiceService {
#Override
public void onCreate() {
super.onCreate();
addRetrofitInterface(TestAPI.class);
}
#Override
protected String getServerUrl() {
return "http://192.168.0.2";
}
}
TestAPI.java:
public interface TestAPI {
#GET("/test")
ResponseData getTest();
}
TestRequest.java:
public class TestRequest extends RetrofitSpiceRequest<ResponseData, TestAPI> {
public TestRequest() {
super(ResponseData.class, TestAPI.class);
}
#Override
public ResponseData loadDataFromNetwork() throws Exception {
ResponseData response;
try {
response = getService().getTest();
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
return response;
}
}
The NoSuchMethodError is happening because HttpURLConnectionImpl is trying to invoke a constructor on HttpEngine that is not defined. Now your project depends on:
com.octo.android.robospice:robospice-retrofit:1.4.14
Which depends on:
com.squareup.retrofit:retrofit:1.6.1
Which depends on both:
com.squareup.okhttp:okhttp:2.0.0
and
com.squareup.okhttp:okhttp-urlconnection:2.0.0
As of version 2.0.0, HttpURLConnectionImpl is in the okhttp-urlconnection module and HttpEngine is in the okhttp module.
The retrofit.client.UrlConnectionClient portion of your stack trace correctly matches retrofit:1.6.1, but the com.squareup.okhttp.internal.huc.HttpURLConnectionImpl portion doesn't match okhttp-urlconnection:2.0.0; it matches okhttp-urlconnection:2.4.0. The constructor that the NoSuchMethodError is complaining about is indeed defined in okhttp:2.4.0, so that means there is a different HttpEngine on your classpath, most likely from a different version of okhttp that is getting pulled in by a transitive dependency. You should be able to fix the problem by specifying the following dependencies:
compile('com.squareup.okhttp:okhttp:2.0.0') {
force = true
}
compile('com.squareup.okhttp:okhttp-urlconnection:2.0.0') {
force = true
}
If that doesn't work for whatever reason, the solution to your problem will still be to get your versions of okhttp and okhttp-urlconnection synced up and at a version that is compatible with your version of retrofit. Declare dependencies similar to above, find the jars that contain HttpURLConnectionImpl and HttpEngine, figure out how they're getting pulled in to your build, and use exclusions to get rid of problematic transitive dependencies.

Android local unit test - new View(null) returns null

I'm experimenting with local android tests (the ones that run on the local JVM, not on the device). I understand that classes in android.jar can't be used properly unless they are mocked, but for some reason when I try to instantiate a new View (or my own class that is inherited from the View)- I get null. This totally breaks my belief that "new" never returns null in Java.
Here's my test code (src/test/java/com/example/FooTest.java):
package com.example;
import android.view.View;
import org.junit.Test;
public class FooTest {
#Test
public void testView() {
View v = new View(null);
System.out.println("view = " + v);
}
}
This code prints in the test report output that the view is null.
Here's my build.gradle (the rest of the project is just generated by android create project and is of no interest):
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3+'
}
}
apply plugin: 'android'
android {
compileSdkVersion 'android-22'
buildToolsVersion '23.0.0 rc2'
buildTypes {
release {
minifyEnabled false
proguardFile getDefaultProguardFile('proguard-android.txt')
}
}
testOptions {
unitTests.returnDefaultValues = true
}
}
repositories {
jcenter();
}
dependencies {
testCompile 'junit:junit:4.12+'
}
Can anyone give some clues about what kind of magic is happening here?
This code only tells that the string representation (i.e. see toString() of View) is "null" not that v is null.
AFAIK, new Something() is never null in java

Categories