I faced this dilemma recently, when I was preparing first release of Cerberus utility for Android. On one hand, in Cerberus I used a tiny subset of Guava features which can be trivially rewritten in vanilla Java in 15 minutes, so maybe I should not force Guava down peoples throat? On the other hand I'm a huge fan of Guava and I think you should definitely use it in anything more complicated than "Hello, world!" tutorial, because it either reduces a boilerplate or replaces your handrolled utilities with better, faster and more thoroughly tested implementations.
The "this library bloats my apk" argument is moot, because you can easily set up the ProGuard configuration which only strips the unused code, without doing any expensive optimizations. It's a good idea, because the dex input will be smaller, which speeds up the build and the apk will be smaller, which reduces time required to upload and install the app on the device.
I found the problem though, which is a bit harder to solve. Modern versions of Guava use some Java 1.6 APIs, which are available from API level 9, so when you try to use it on Android 2.2 (API level 8), you'll get the NoSuchMethodException or some other unpleasant runtime error (side note: position #233 on my TODO list was a jar analyzer which finds this problem). On Android 2.2 you're stuck with Guava 13.0.1.
This extends also to Guava as a library dependency. If one library supports Android 2.2 and older, it forces old version of Guava as dependency. And if another library depends on more recent version of Guava, you're basically screwed.
One conclusion you can draw from this blog post is that you shouldn't use Guava in your open source libraries to prevent dependency hell, but that's spilling the baby with the bathwater. The problem is not Guava or any other library, the problem are Java 1.6 methods missing from Android API level 8! The statistics from Google indicates that Froyo is used by 1.6%, in case of Base CRM user base it's only 0.2%. So more reasonable course of action is finally bumping minSdkVersion to 10 (or event 14), both for your applications and all the libraries.
Showing posts with label fragmentation. Show all posts
Showing posts with label fragmentation. Show all posts
Friday, December 27, 2013
Wednesday, September 5, 2012
Android SharedPreferences gotcha
I have another gotcha for you. Can you tell what's wrong with the following code?
Fortunately the obvious crashlog is obvious and you can solve this issue in about 5 seconds, by wrapping the UI action in Activity.runOnUiThread() method. Morbidly curious may track the root cause of this issue in GrepCode. Tl;dr: before Gingerbread the listeners are notified in the same thread as the SharedPreferences.commit() caller, in later releases commit() ensures the notifications are performed in UI thread.
Code of the sample application that demonstrates this issue is available on my github.
public class MyFragment extends Fragment implements OnSharedPreferenceChangeListener {
private TextView mInfo;
private SharedPreferences mPreferences;
public static final String INFO_SP_KEY = "info";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.my_fragment, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mInfo = (TextView) view.findViewById(R.id.info);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(INFO_SP_KEY)) {
updateInfo();
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
}
@Override
public void onPause() {
super.onPause();
mPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onResume() {
super.onResume();
mPreferences.registerOnSharedPreferenceChangeListener(this);
updateInfo();
}
protected void updateInfo() {
mInfo.setText(getString(R.string.info_text, mPreferences.getInt(INFO_SP_KEY, 0)));
}
}
At first glance everything looks fine and in most cases it will work fine as well. However, if you a) set android:minSdkVersion to 8 or lower and b) change the shared preference from another thread (IntentService, SyncAdapter, etc.), you'll get the following crash:09-05 07:16:58.993: E/AndroidRuntime(403): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.ViewRoot.checkThread(ViewRoot.java:2802) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.ViewRoot.requestLayout(ViewRoot.java:594) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.view.View.requestLayout(View.java:8125) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.checkForRelayout(TextView.java:5378) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2688) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2556) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.widget.TextView.setText(TextView.java:2531) 09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.BaseFragment.updateButtonText(BaseFragment.java:65) 09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.WrongFragment.onSharedPreferenceChanged(WrongFragment.java:12) 09-05 07:16:58.993: E/AndroidRuntime(403): at android.app.ContextImpl$SharedPreferencesImpl$EditorImpl.commit(ContextImpl.java:2830) 09-05 07:16:58.993: E/AndroidRuntime(403): at com.porcupineprogrammer.sharedpreferencesgotcha.BaseFragment$1$1.run(BaseFragment.java:36) 09-05 07:16:58.993: E/AndroidRuntime(403): at java.lang.Thread.run(Thread.java:1096)
Fortunately the obvious crashlog is obvious and you can solve this issue in about 5 seconds, by wrapping the UI action in Activity.runOnUiThread() method. Morbidly curious may track the root cause of this issue in GrepCode. Tl;dr: before Gingerbread the listeners are notified in the same thread as the SharedPreferences.commit() caller, in later releases commit() ensures the notifications are performed in UI thread.
Code of the sample application that demonstrates this issue is available on my github.
Thursday, August 30, 2012
Custom scrollbar graphics in Android
One of the UI elements you might want to customize in your app are scrollbars for ListViews, ScrollViews and other scrollable objects. It's a minor visual change, but it might give your app more consistent and polished look and feel, especially in case of the heavily branded UI.
Anyways, changing the default scrollbar style is dead simple - you just need to specify new drawables for the track and thumb in your style:
Of course, there is a small gotcha (why else would I bother to write this blog post?). Let's say that you don't need to customize horizontal scrollbar, so you prepare only vertical 9-patches:
On Ice Cream Sandwich everything looks fine, but on Gingerbread the graphics are not exactly what you want:
Quick Google search returned a StackOverflow thread with a description and link to Android bug tracker, but no full workaround. If you're too lazy to click on those links, on Gingerbread and earlier releases the View asks ScrollbarDrawable for the height of horizontal scrollbar and uses it as a horizontal scrollbar height and vertical scrollbar width. Let's modify our scrollbar graphics a bit:
And apply it as both horizontal and vertical scrollbar.
Note: in general case you probably want to create another graphics for horizontal scrollbar by rotating and flipping vertical scrollbar graphics. Our scrollbar graphics doesn't have any non-symmetric elements and I'm lazy, so I used the same 9-patch for both scrollbars.
Anyways, changing the default scrollbar style is dead simple - you just need to specify new drawables for the track and thumb in your style:
<style name="badList"> <item name="android:scrollbarThumbVertical">@drawable/scrollbar_handle</item> <item name="android:scrollbarTrackVertical">@drawable/scrollbar_track</item> </style>
Of course, there is a small gotcha (why else would I bother to write this blog post?). Let's say that you don't need to customize horizontal scrollbar, so you prepare only vertical 9-patches:
On Ice Cream Sandwich everything looks fine, but on Gingerbread the graphics are not exactly what you want:
Quick Google search returned a StackOverflow thread with a description and link to Android bug tracker, but no full workaround. If you're too lazy to click on those links, on Gingerbread and earlier releases the View asks ScrollbarDrawable for the height of horizontal scrollbar and uses it as a horizontal scrollbar height and vertical scrollbar width. Let's modify our scrollbar graphics a bit:
And apply it as both horizontal and vertical scrollbar.
<style name="goodList"> <item name="android:scrollbarThumbVertical">@drawable/scrollbar_handle</item> <item name="android:scrollbarTrackVertical">@drawable/scrollbar_track</item> <item name="android:scrollbarThumbHorizontal">@drawable/scrollbar_handle</item> <item name="android:scrollbarTrackHorizontal">@drawable/scrollbar_track</item> </style>
Lo and behold, it works!
Note: in general case you probably want to create another graphics for horizontal scrollbar by rotating and flipping vertical scrollbar graphics. Our scrollbar graphics doesn't have any non-symmetric elements and I'm lazy, so I used the same 9-patch for both scrollbars.
The code of the sample application can be found here.
Subscribe to:
Posts (Atom)



