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);
Related
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.
I am using BiometricManager (Introduced in Api 29) in a project with a minSdkVersion of 26.
To my surprise, the project is compiling and running, I would have expected this to throw an error at buildtime. Am I missing something? Will this cause issues on release?
Gradle:
defaultConfig {
minSdkVersion 26
targetSdkVersion 31
versionCode 1
versionName "1.0"
Class:
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
public BiometricPromptClass(Activity activity) {
this.context = activity.getBaseContext();
this.activity = activity;
}
public int getDeviceBiometricStatus(){
return getBiometricManager().canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
}
public boolean CheckIfCanAuthenticate() {
if (getDeviceBiometricStatus() == BiometricManager.BIOMETRIC_SUCCESS) {
return true;
} else {
failureCode = getDeviceBiometricStatus();
return false;
}
}
I am using BiometricManager
No, you are not... at least, not the one that you linked to. You linked to android.hardware.biometrics.BiometricsManager. Your code uses androidx.biometrics.BiometricsManager. Those are not the same class. The Jetpack (androidx) edition will have code that tries to support older devices gracefully.
I'm learning how to develop apps for Android, I´m familiar with Java through school courses. Currently, Im developing a small TicTacToe app for exercise purpose and I´m trying to show a cross instead of a circle but Android Studio cant compilesetImageRecource` command even though in every damn online forum this method is recommended.
I've already tried things like set setBackgroundRecource but all these things doesn't fulfill my purpose...
The Java Code:
public void click(View pView){
if(getActivePlayer()== 1){
if(s1.getSymbol().equals("cross")){
pView.setImageResource(R.drawable.circle); //claims cannot Resolve method 'setIMageRecource(int)'
}
else{
pView.setImageResource(R.drawable.cross); //claims cannot Resolve method 'setIMageRecource(int)'
}
}
else if(getActivePlayer()==2){
if(s2.getSymbol().equals("circle")){
pView.setImageResource(R.drawable.circle); //claims cannot Resolve method 'setIMageRecource(int)'
}
else{
pView.setImageResource(R.drawable.cross); //claims cannot Resolve method 'setIMageRecource(int)'
}
}
pView.setY(-1000);
pView.setAlpha(1);
pView.animate().translationYBy(1000).setDuration(1000);
if (getActivePlayer() == 1){
setActivePlayer(2);
String checker = "aktive player ist gerade" + getActivePlayer();
Log.i("INfo", checker);
}
else{
setActivePlayer(1);
String checker = "aktive player ist gerade" + getActivePlayer();
Log.i("INfo", checker);
}
}
In XML:
<ImageView
android:id="#+id/field4"
android:layout_width="120dp"
android:layout_height="120dp"
android:onClick="click"
app:srcCompat="#drawable/circle" />
You need to cast View to ImageView like this.
((ImageView) pView).setImageResource(R.drawable.cross);
You can use ImageView.setImageDrawable(Context impact.getDrawable(mContext, R.drawable.cross));
Here mContext is globally declared as your activity reference.
I'm making an Android app coding in Java just like everybody else's do. Since Android Studio 3.o Canary was released and adding support for Kotlin, I took a chance to give a try. Downloaded the plugins and setup the Gradle file correctly.
But once the activity is converted into Kotlin and synced, an error occured.
Below is my build.gradle,
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
and,
ext.kotlin_version = '1.1.3'
Dependencies, [
So, Im thinking to go back to Java until the issue is solved.
The Kotlin code are,
class Welcome : AppCompatActivity() {
internal var rujuk = FirebaseDatabase.getInstance().reference /*3rd step, DB reference*/
/*4th, initially write under onStart method, then CnP here, value inside child() should be same as in DB.*/
internal var referKpdTeksView = rujuk.child("intro")
#BindView(R.id.buku) internal var buku: ImageView? = null
#BindView(R.id.wel) internal var teksTajuk: TextView? = null /*1st step, declare variable for each Text*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_welcome)
ButterKnife.bind(this)
rujuk.keepSynced(true)
Glide.with(this).load("https://firebasestorage.googleapis.com/v0/b/travel-and-go-93552.appspot.com/o/buku.png?alt=media&token=bad59236-e4ff-44e0-81ac-32adf9c1aea4").diskCacheStrategy(DiskCacheStrategy.SOURCE).into(buku!!)
}
#OnClick(R.id.enterButton)
fun g() {
val EnterButton = Intent(this#Welcome, CountryList::class.java)
startActivity(EnterButton)
}
/*5th step, create onStart method*/
override fun onStart() {
super.onStart()
/*DB reference 4th step
* Syntax;
* DatabaseReference.addValueEventListener(new ValueEventListener)*/
referKpdTeksView.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val ayat = dataSnapshot.getValue(String::class.java)
teksTajuk!!.text = ayat
}
override fun onCancelled(databaseError: DatabaseError) {
}
})
}
}
and the error is,
Error:Failed to delete original file 'C:\Users\MohdA\AppData\Local\Temp\gradle_download1285409691272083864bin' after copy to 'C:\Users\MohdA.gradle\caches\modules-2\files-2.1\com.android.databinding\compilerCommon\2.3.3\1f0e06d55f3f72f3192b6e026d9a5a557d9e2ea6\compilerCommon-2.3.3.jar'
In intellij i resolved by going through show history , project right click local history then show history
I cannot retrieve Place Details using the Google Places API on Android. I'm calling getPlacyById() for specific place_id.
Inside an Android activity I setup a googleApiClient and call connect(). The apiClient connects successfully.
Then for testing purposes I call getPlaceById() using a hardcoded place_id and a resultCallback. However, the resultCallback never gets called.
The hardcoded place_id is tested through javascript (using the javascript Google Maps API) and returns valid data. but the Java code below fails to return any data.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.some_activity_layout);
ButterKnife.inject(this);
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
if (mGoogleApiClient == null) {
rebuildGoogleApiClient();
}
.....
code_for_activity_init
.....
}
protected synchronized void rebuildGoogleApiClient() {
// When we build the GoogleApiClient we specify where connected and connection failed
// callbacks should be returned, which Google APIs our app uses and which OAuth 2.0
// scopes our app requests.
mGoogleApiClient = new GoogleApiClient.Builder(this)
//.enableAutoManage(this, 0 /* clientId */, this)
.addConnectionCallbacks(this)
.addApi(Places.GEO_DATA_API)
.build();
}
#Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
#Override
protected void onStop() {
super.onStop();
mGoogleApiClient.disconnect();
}
#Override
public void onConnected(Bundle bundle) {
// Successfully connected to the API client. Pass it to the adapter to enable API access.
//addressesAdapter.setGoogleApiClient(mGoogleApiClient);
Log.i("place api conn", "GoogleApiClient connected.");
}
#Override
public void onConnectionSuspended(int i) {
// Connection to the API client has been suspended. Disable API access in the client.
//mAdapter.setGoogleApiClient(null);
Log.e("place api conn", "GoogleApiClient connection suspended.");
}
#OnLongClick(R.id.one_more_request)
public boolean getDataAgain(){
Log.d("place", "getPlace By Id");
PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
.getPlaceById(mGoogleApiClient, "ChIJIV6Mkke9oRQRcPq3KAx513M");
placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
return true;
}
private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
= new ResultCallback<PlaceBuffer>() {
#Override
public void onResult(PlaceBuffer places) {
if (!places.getStatus().isSuccess()) {
// Request did not complete successfully
Log.e("place", "Place query did not complete. Error: " + places.getStatus().toString());
return;
}
// Get the Place object from the buffer.
final Place place = places.get(0);
// Format details of the place for display and show it in a TextView.
//mPlaceDetailsText.
Toast.makeText(MyActivity.this,/*setText(*/
formatPlaceDetails(getResources(), place.getName(),
place.getId(), place.getAddress(), place.getPhoneNumber(),
place.getWebsiteUri()), Toast.LENGTH_LONG).show();
Log.i("place", "Place details received: " + place.getName());
}
};
this question is similar to Google Places Api resultCallback not firing however I have checked that my AndroidManifest already includes the the required permission
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
Ultimately I would like the getPlaceById() to be called every time the user selects an item from an autocomplete Address field.
***** UPDATE *******
Solution found. I was missing two things. First, copy-paste the maps.v2.API_KEY this time as geo.API_KEY (and make sure you delete the maps.v2.API_KEY, because it cannot be specified twice)
android:name="com.google.android.maps.v2.API_KEY" android:value="whatEverGoogleMapsApiKeyYouHave"
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="whatEverGoogleMapsApiKeyYouHave"/>
and secondly I had to enable Google Places API for Android
UPDATE for SDK 21
If you have both meta-data's included you will get the following error:
Caused by: java.lang.RuntimeException: The API key can only be specified once.
Instead just indlude the geo API key:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="whatEverGoogleMapsApiKeyYouHave"/>
and NOT the maps one
This exactly what I've been struggling with. In Android Studio when I added the 2nd API key I received an exception:
Caused by: java.lang.RuntimeException: The API key can only be specified once.
This was resolved by just adding the "com.google.android.geo.API_KEY" meta-data entry to the Manifest file and removing the maps one.
I was missing two things.
First of all the com.google.android.geo.API_KEY which is the same as the com.google.android.maps.v2.API_KEY. Just copy paste the API KEY on the AndroidManifest file.
android:name="com.google.android.maps.v2.API_KEY" android:value="whatEverGoogleMapsApiKeyYouHave"
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="whatEverGoogleMapsApiKeyYouHave"/>
I was also missing the Google Places API for Android Service on google console. If you have Google Places API enabled , beware, this in not enough, because it's simply a different API. You have to go to google console and explicitly enable the Google Places API for Android.
****** EDIT ********
As #Lissy pointed out, from SDK 21 onwards, defining the same API_KEY twice will give an error. Keeping the geo.API_KEY will suffice