As part of an app I'm working on, I'm attempting to obtain a reference to the ActionBar's container view. I used the answer from this question, and it's working nicely for me as long as the Android device in question is running API level 11 or up. However, the app needs to work as far back as API level 9 (the target API level is 19), and Gingerbread devices are giving me problems. I was originally using Sherlock for the project, but recently made the switch over to the v7 compat library instead. I can see and interact with the action bar in normal ways on Gingerbread devices (buttons work, etc.), but it fails when I attempt to get the container. The code I'm using is this (note - it's running inside a subclass of ActionBarActivity):
private FrameLayout getActionBarContainer() {
FrameLayout result = null;
int resId = getResources().getIdentifier("action_bar_container", "id", "android");
try {
result = (FrameLayout)getWindow().getDecorView().findViewById(resId);
}
catch (Exception e) {
// If we get an exception, just eat it
}
return result;
}
To answer a few questions before they get asked:
resId resolves to a proper ID value on v11 devices and up, but resolves to 0 on pre-v11 devices.
The code that uses this has proper checks to handle a null result, which is why I'm just eating the exceptions. The try/catch block is mostly just there in case, by some freak occurrence, a ClassCastException manages to get thrown (which it never should, since the container is a subclass of FrameLayout).
I've checked and re-checked my imports; all of my ActionBar references (and all things related, like the ActionBarActivity superclass I'm extending) are the v7 compat library versions, not the standard library versions.
The action_bar_container ID should exist within the v7 compat library, if this is any indication.
I'm about out of ideas at this point. Is there something simple I'm missing? Any suggestions will be appreciated, and if you need more context/clarification, let me know.
I think your error is on the last parameter of
int resId = getResources().getIdentifier("action_bar_container", "id", "android");
For api level <11 the package should be your application's package and not the plataform's package "android"
Related
Getting,
no method with name='setBackground' signature
or
no method with name='setBackground'
upon changing .Background property (to change the background visuals) in the app running on legacy Android versions (<4.1)
Any ideas on how to fix this?
As this question about Eclipse suggests, setBackground is problematic on API below level 16. Unlike covered in the question though, to work around the issue AND have your code Android 4.0-compatible, you'd want to change your background with the following (reproducible logic)
if (Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.JellyBean)
{
layout.SetBackgroundDrawable(gd);
}
else
{
layout.Background = gd;
}
I have a button that I want to set the background of using a png file from internal storage. For android api 16 and up, this works fine:
filePath = getActivity().getFileStreamPath(colorCodes.get(i-1));
temp.setBackground(Drawable.createFromPath(filePath.toString()));
When running on an android tablet with 4.0.4, this part crashes the app with a nosuchmethod error (setBackground). After a little research, I see that setBackground is only available for api 16+. After looking around on SO and a few other places, it looks like I need to use setBackgroundDrawable (deprecated) or setBackgroundResource. I tried this:
filePath = getActivity().getFileStreamPath(colorCodes.get(i-1));
if (android.os.Build.VERSION.SDK_INT < 16) {
temp.setBackgroundDrawable(Drawable.createFromPath(filePath.toString()));
} else {
temp.setBackground(Drawable.createFromPath(filePath.toString()));
}
When logging it out, it shows that setBackgroundDrawable is running and not setBackground, but I get the same nosuchmethod error (setBackground).
The other option is setBackgroundResource, but it accepts an int and not a drawable. Can I convert from drawable to int for this purpose?
What can I do here to set the background of the button to a file in internal storage for APIs < 16?
Thanks.
***EDIT - ok, this is working. just missed a little part elsewhere in the code that had the same problem. However, is using a deprecated method really the only way?
Deprecation is a status applied to a computer software feature,
characteristic, or practice indicating it should be avoided, typically
because of it being superseded. The term is also sometimes used for a
feature, design, or practice that is permitted but no longer
recommended in other areas, such as hardware design or compliance to
building codes. (source link)
Now we can answer your question.
Before API level 16 there is a method named setBackgroundDrawable. After API Level 16 google decided to write a new method setBackground for same purpose and recommend us to use new method. (Reason of this may be found by googling.)
You can use setBackgroundDrawable method for all api levels. There aren't any constraint for this. But using new method setBackground is recommended after API Level 16.
But you can only use setBackground method for devices which is running on API Level 16 or higher. So if you only implement setBackground method in your code, you are going to get MethodNotFoundException for devices which run below API Level 16.
To sum up; it is a best practice(for me it is a must) to use new methods then deprecated ones with supportted api version check such as;
if (android.os.Build.VERSION.SDK_INT < 16) {
temp.setBackgroundDrawable(Drawable.createFromPath(filePath.toString()));
} else {
temp.setBackground(Drawable.createFromPath(filePath.toString()));
}
I am not quite sure whether it is the only way to achieve this but in my opinion it is the correct one. Because the annotation #Deprecated defines the method to be superseded (in most cases) it automatically implies you can (I would even say should) use it to address older versions which are the targeted versions of this method.
I have my app that requires SDK 9+ with code containing setBackgroundDrawable() which is API level 16. I did not get any error while coding or building the apk. but I got about 50 reports of this error happening in google analytics and a few reports in my developers console.
When I run the lint checker It also doesn't warn me. I am using eclipse. Is there a reason why it doesn't fail to compile like usually when you add a method that's not supported by the minimum API or is it simply an eclipse bug?
First of all you get no error when building since you probably are building with SDK 16+ and the method is there. But if you install the apk to a 2.1 Android phone it will throw a MethodNotFound Exception. So in the future ALWAYS install your apk on a min-target device to see if you didn't forget something. Min-Target basically is only a filter for the PLAY store (and for lint warnings, etc.)
AFAIK moving from imageView.setBackground(...) to imageView.setBackgroundDrawable(...) was just an api style design choice. So if you look at the source of Android SDK 18 you will see:
/**
* Set the background to a given Drawable, or remove the background. If the
* background has padding, this View's padding is set to the background's
* padding. However, when a background is removed, this View's padding isn't
* touched. If setting the padding is desired, please use
* {#link #setPadding(int, int, int, int)}.
*
* #param background The Drawable to use as the background, or null to remove the
* background
*/
public void setBackground(Drawable background) {
//noinspection deprecation
setBackgroundDrawable(background);
}
So for now its absolutely irrelevant if you use one or the other - but of course this COULD change (but it's unlikely it does in the future since it would break nearly every app done before SDK 16) - basically it's fine to use setBackground() even on SDK 18+
So if you want to be on the future-proof but ugly side you could use a version fork depicted by the other answers
if(Build.VERSION.SDK_INT >= 16) {
//new code
} else {
//deprecated code
}
Just one thing, and maybe this is a personal style preference, I would not suppress Lint warnings with annotations like this:
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
I like to keep the warnings since maybe later if I want to refactor/move to higher SDK I could easily get rid of these ugly switches.
Update:
Google's v4 support library contains helper classes for sdk boiler plate code. In this instance you would use:
ViewCompat.setBackground(view,drawable);
which handles the SDK check for you.
Seems to be a bug in eclipse or your eclipse is not working perfectly. But you can try project clean and try. But code wise you can try something like this:
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
public void setImage(ImageView imageView, BitmapDrawable bd) {
if(Build.VERSION.SDK_INT > 16) {
imageView.setBackground(bd);
} else {
imageView.setBackgroundDrawable(bd);
}
}
You can call this function along with ImageView and bitmap drawable.
Is there any kind of conditional compiling for Android?
I had to make my project for Android 3 (API 11) just because ExifInterface has almost no useful attributes in Android 2.3 (API 10), despite the fact that it appeared in API 5 (!!??). I don't want to restrict my app to ICS users.
Thanks!
You can check dynamically the current API version of the device and do different stuff depending on that:
if(Build.VERSION.SDK_INT < 14) {
// Crappy stuff for old devices
}
else {
// Do awesome stuff on ICS
}
But be careful that if you need to instantiate classes that are not available for all APIs then you should do it in a runnable or in a separate wrapper class, e.g:
if(Build.VERSION.SDK_INT < 14) {
// Crappy stuff for old devices
}
else {
// Do awesome stuff on ICS
new Runnable() {
new AmazingClassAvailableOnICS();
(...)
}.run();
}
import android.annotation.TargetApi;
and then use annotations:
#TargetApi(11)
public void methodUsesAPI11()
{
...
Using this trick does a very simple thing: it allows compiling some code which contains API level 11 calls (classes, methods, etc) and still set android:minSdkVersion="8" in the manifest. Nothing more, nothing else.
The rest is up to you. You must check platform version before you call methodUsesAPI11() or you handle exceptions in order to prevent app crash and perform other action on older platforms.
Checking Build.VERSION.SDK_INT or using annotations should suffice, however, this link I'd bookmarked might be relevant to your case:
http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html?m=1
You can use what they describe there to have classes that may not be compatible, but will never be loaded. It's not conditional compilation, but it may be what you need, however, it is a bit more complex.
So we've seen the preview sdk and the neat new stuff like ActionBar and Fragments. Making a lot of method calls will be unavoidable to make use of these, so what strategies are there for maintaining 1 version of the app, which will let me use all the snazzy new stuff but also work on devices running 2.3 or below? My app targets 1.5 - 2.3 at the moment.
The same fragment APIs are now available as a static library for use with older versions of Android; it's compatible right back to Android 1.6.
There are a few tricks you can use to see if the various new APIs are available to your app. Generally speaking, you'll probably want to create two alternative sets of Activities, one that uses the fancy new APIs (ActionBar, Animators, etc.) -- and another set that don't.
The following code shows how you can use reflection and exception catching to determine the availability of the Fragment APIs, and version checking to confirm if the other Honeycomb APIs are available.
private static boolean shinyNewAPIsSupported = android.os.Build.VERSION.SDK_INT > 10;
private static boolean fragmentsSupported = false;
private static void checkFragmentsSupported() throws NoClassDefFoundError {
fragmentsSupported = android.app.Fragment.class != null;
}
static {
try {
checkFragmentsSupported();
} catch (NoClassDefFoundError e) {
fragmentsSupported = false;
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent startActivityIntent = null;
if (!shinyNewAPIsSupported)
startActivityIntent = new Intent(this, MainNonActionBarActivity.class);
else
startActivityIntent = new Intent(this, MainActionActivity.class);
startActivity(startActivityIntent);
finish();
}
Generally speaking you can use the same layout definitions. Where Fragments are available you'll inflate each layout within a different Fragment, where they aren't you'll probably want to use <include> tags to embed several of them into a more complex Activity layout.
A more detailed work through of how to write the code to support backwards compatibility on Honeycomb can be found here: http://blog.radioactiveyak.com/2011/02/strategies-for-honeycomb-and-backwards.html
Conveniently, Google's Dianne Hackborne has posted a blog entry covering this exact topic. Google say they'll be providing static libraries so older versions of Android will also be able to use fragments.
You might find Reto Meier's article on backwards-compatibility useful, specifically the section headed "Dealing with missing classes".
I've yet to look at the Honeycomb SDK myself but I, like you, am hoping it's pretty easy and hassle-free to make use the new features without jeopardising compatibility with older devices.
Well google just announced honeycomb will be tablet only: http://www.pcmag.com/article2/0,2817,2379271,00.asp
So if your device is meant for mobile only this may not even be an issue.
Official Android sample that will help you achieve ActionBar from 1.6 to 4.x