TimePickerDialog and AM/PM vs 24 Hour format

Just a short article to maybe help other people not spend 1 hour searching the web for the answer like I did.

The Android SDK provides you with 2 really well made dialog pickers for respectively a date and a time : DatePickerDialog and TimePickerDialog.

The constructors for DatePickerDialog are pretty much straight-forward.
However the one for TimePickerDialog has a small problem :

As you can see in the screenshot above, you need to specify through the is24HourView parameter whether you want to display a time with hours from 0 to 23 (used in France and Germany for example) or you want to display a time with hours from 1 to 12 and also AM/PM selector (used in the USA for example).

The main problem is that I don’t want to choose that in my code. I want it to be linked to the user locale settings like everythink else.
I don’t want why it was coded like that but here is how to link this parameter to the user locale :

    new TimePickerDialog(this, mOnTimeSetListener, time.hour, time.minute, DateFormat.is24HourFormat(this));

The method is24HourFormat from DateFormat allows us to retrieve the user setting.

Android + LEGO = Win ?

Update 08/07/2012 : After 24 hours, the project has already reached 2600+. 1/4 of the task is already done ! Now we just need to continue to talk about it so we reached rapidly the 10 000 mark ! :D

For the past years, I have been a developer as well as a huge fan of Android. I also have always played with LEGO (when I was young and even still now)

What if we could combine these 2 worlds ?!

That’s what Marc Young must have been thinking when he started his project !

This project looks awesome with even rotating arms, heads and antennas. And frankly I want one for myself ! :D

There is only one problem… This project is part of the CUUSOO system which is a way for people to present project to LEGO for future creation.
And before an idea can be valid, it needs to receive 10 000 votes.

So it would be great if you could vote for this project (it’s really fast and easy to do).

Small issue with ADT 20 and SDK 16 and how to fix it

The latest version of the Android Developer Tools (ADT) brought a lot of improvements for us developers. Sadly there is a little quirk coming with it too.

If you have a project targeting API 16/Jelly Bean and you are using ADT 20, you might have some problems with the Graphical Layout tool. Instead of displaying the UI, you may end up with the following screen :

The error message is java.util.LinkedHashMap.eldest()Ljava/util/Map$Entry; which is totally clear… or not.
By looking inside Eclipse, we can find the corresponding complete Stacktrace :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java.lang.NoSuchMethodError: java.util.LinkedHashMap.eldest()Ljava/util/Map$Entry;
	at android.util.LruCache.trimToSize(LruCache.java:206)
	at android.util.LruCache.evictAll(LruCache.java:306)
	at android.widget.SpellChecker.resetSession(SpellChecker.java:141)
	at android.widget.SpellChecker.setLocale(SpellChecker.java:147)
	at android.widget.SpellChecker.<init>(SpellChecker.java:112)
	at android.widget.Editor.updateSpellCheckSpans(Editor.java:461)
	at android.widget.Editor.onAttachedToWindow(Editor.java:212)
	at android.widget.TextView.onAttachedToWindow(TextView.java:4455)
	at android.view.View.dispatchAttachedToWindow(View.java:11755)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2424)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2424)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2424)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2424)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:2424)
	at android.view.AttachInfo_Accessor.setAttachInfo(AttachInfo_Accessor.java:37)
	at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:329)
	at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:331)
	at com.android.ide.common.rendering.LayoutLibrary.createSession(LayoutLibrary.java:325)
	at com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService.createRenderSession(RenderService.java:372)
	at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.renderWithBridge(GraphicalEditorPart.java:1640)
	at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.recomputeLayout(GraphicalEditorPart.java:1391)
	at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.activated(GraphicalEditorPart.java:1165)
	at com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate.delegatePageChange(LayoutEditorDelegate.java:679)
	at com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor.pageChange(CommonXmlEditor.java:359)
	at org.eclipse.ui.part.MultiPageEditorPart.setActivePage(MultiPageEditorPart.java:1067)

After some research, I found a temporary solution on StackOverflow until this bug is fixed in the next patch of ADT.

The problem seems to be linked to the use of EditText with suggestions in Jelly Bean. On a phone there is no problem but in the Graphical Layout tool it breaks.
We have 2 solutions :

  • Disable the suggestions in the EditText with the inputType textNoSuggestions
    • It fixes the bug but it prevents the users from using the completion in your app. So let’s forget about it and look at the other solution
  • Switch the Graphical Layout to a different API (for example ICS/15 which is nearly the same as JB in terms of UI)

To do that, you just need to use the little droid on the top right and switch to 15 as shown in the screenshot below.

And voila ! :)

Update :
Xavier Ducrohet (who is working on the ADT tool) replied to my post on Google + and told me that the issue doesn’t come from ADT 20 but from the rendering library of Jelly Bean. So if I understand right, we need to wait for an update of JB to have a fix.

Update 2 :
The fix is available in ADT 20.0.1 ! So update the following elements and it will be good :

  • ADT Plugin in version 20.0.1
  • Android SDK Tools 20.0.1
  • Android SDL 16 rev 2

If you don’t see them, clear the cache of the SDK manager in the options

How to add autocompletion to an EditText

Finally after months and months of saying that I would add development articles to my blog, here is the first one :)

EditText is a component that everybody knows and it is the primary way to get user input in an Android application.
What is really less known is its cousin : AutoCompleteTextView. It’s a subclass of EditText that allows you to display possible autocompletions to the user depending on what he/she already entered in the EditText.

This article will focus on how to use this component and how to bind different data sources to provide the completions.

1 – Presentation of AutoCompleteTextView and simple autocompletion

AutoCompleteTextView is used exactly the same way as an EditText in your layouts (as it’s a subclass of EditText). You just need to replace your EditText by an AutoCompleteTextView and this part is done !

Now that our layout is ready, let’s look at the code.

For this first example we are going to use a really simple example (based on the documentation of AutoCompleteTextView class in the Android SDK). The possible autocompletions are going to be a fixed list of countries.

1
2
3
4
5
6
7
8
private static final String[] COUNTRIES = new String[] {
	"Belgium", "France", "Italy", "Germany", "Spain"
};
 
// In the onCreate method
AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.actv_country);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, COUNTRIES);
textView.setAdapter(adapter);

We just have an array of Strings as the data source and we just bind via an ArrayAdapter the list of countries> The component will manage them by itself.

Now let’s look at a little more complex example.

2 – Email autocompletion

Let’s say we have a login screen in our application where the login is an email. An email is quite long and really annoying to type in, especially with a virtual keyboard.

We don’t know all the email addresses the user could potentially have. What we know however from his phone is all the email addresses he/she used as an account (Google, Facebook, Twitter, …).
We can use them as a data source to provide completion.

1
2
3
4
5
6
7
8
9
10
11
12
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$", Pattern.CASE_INSENSITIVE);
 
// In the onCreate method
AutoCompleteTextView editTextLogin = (AutoCompleteTextView) findViewById(R.id.actv_login);
Account[] accounts = AccountManager.get(this).getAccounts();
Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
	if (EMAIL_PATTERN.matcher(account.name).matches()) {
		emailSet.add(account.name);
	}
}
editTextLogin.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, new ArrayList<String>(emailSet)));

This time instead of using a fixed array of Strings, we load the user accounts through the AccountManager.
As some accounts may not have an email address linked to them, we filter them and keep only the ones who matches an email regex. We also use a Set to remove the duplicates.
Then we use the same way as before to bind them to the AutoCompleteTextView through an ArrayAdapter.

One thing to know if you want to use this snippet of code as is in your project : it requires the permission android.permission.GET_ACCOUNTS to be able to read the user accounts.

You can use this example with any other data source as long as you manage to get an array of Strings to display.

And here is how it looks :

3 – Webservice autocompletion

For the first 2 examples, we only used static data. Now let’s use dynamic data for the auto-completion. The data is going to be returned from a web service which is going to be called every time the user input changes in the field.
For this example, we are going to write the code to add autocompletion to an address field :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
AutoCompleteTextView editTextAddress = (AutoCompleteTextView)findViewById(R.id.actv_address);
editTextAddress.setAdapter(new AutoCompleteAdapter(this));
 
// And the corresponding Adapter
private class AutoCompleteAdapter extends ArrayAdapter<Address> implements Filterable {
 
	private LayoutInflater mInflater;
	private Geocoder mGeocoder;
	private StringBuilder mSb = new StringBuilder();
 
	public AutoCompleteAdapter(final Context context) {
		super(context, -1);
		mInflater = LayoutInflater.from(context);
		mGeocoder = new Geocoder(context);
	}
 
	@Override
	public View getView(final int position, final View convertView, final ViewGroup parent) {
		final TextView tv;
		if (convertView != null) {
			tv = (TextView) convertView;
		} else {
			tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
		}
 
		tv.setText(createFormattedAddressFromAddress(getItem(position)));
		return tv;
	}
 
	private String createFormattedAddressFromAddress(final Address address) {
		mSb.setLength(0);
		final int addressLineSize = address.getMaxAddressLineIndex();
		for (int i = 0; i < addressLineSize; i++) {
			mSb.append(address.getAddressLine(i));
			if (i != addressLineSize - 1) {
				mSb.append(", ");
			}
		}
		return mSb.toString();
	}
 
	@Override
	public Filter getFilter() {
		Filter myFilter = new Filter() {
			@Override
			protected FilterResults performFiltering(final CharSequence constraint) {
				List<Address> addressList = null;
				if (constraint != null) {
					try {
						addressList = mGeocoder.getFromLocationName((String) constraint, 5);
					} catch (IOException e) {
					}
				}
				if (addressList == null) {
					addressList = new ArrayList<Address>();
				}
 
				final FilterResults filterResults = new FilterResults();
				filterResults.values = addressList;
				filterResults.count = addressList.size();
 
				return filterResults;
			}
 
			@SuppressWarnings("unchecked")
			@Override
			protected void publishResults(final CharSequence contraint, final FilterResults results) {
				clear();
				for (Address address : (List<Address>) results.values) {
					add(address);
				}
				if (results.count > 0) {
					notifyDataSetChanged();
				} else {
					notifyDataSetInvalidated();
				}
			}
 
			@Override
			public CharSequence convertResultToString(final Object resultValue) {
				return resultValue == null ? "" : ((Address) resultValue).getAddressLine(0);
			}
		};
		return myFilter;
	}
}

As the data is dynamic depending on the current value, we can’t just provide beforehand the list of elements. Instead we create an ArrayAdapter with a special Filter.
Everytime the input changes, the performFiltering method is called and we retrieve in it the corresponding values from the webservice. These values are then transfered through the FilterResults object to the publishResults method where we replace the current elements with the new ones.

In our specific case, our webservice is the SDK Geocoder which calls the Maps server to get possible addresses based on input provided by the user.
As the result type, Address, is a complex object and we only want to display its formatted address while still keeping the object in the Adapter for further use, we also have to override both getView and convertResultToString methods :

  • The first one, getView, to choose how to display our special object in the list of possible autocompletions.
  • convertResultToString is used internally by the AutoCompleteTextView to choose what to display in the EditText field when the user selects one of the completions (in our case, the formatted address).

If the data returned by your webservice is only a String, you don’t need to do that (except if you want a special format, like upper case or something).

One thing to remember, the code in the performFiltering method is executed in a worker thread so we can directly access the webservice (in our case the Geocoder) to get the corresponding results even if it’s a long process.

4 – Conclusion

Normally now you know how to add autocompletion to your applications :)

If you have some questions about it, feel free to comment this article or to contact me via email !

And I have made a small application containing all these examples. Here is the APK and the project source code.

How to fix the “NoClassDefFoundError” with ADT 17

So I updated my SDK and ADT to version 17 and once again I got a big problem :)

Every project I have which use JAR libraries could still be built without problems but as soon as they start on the phone, they crashed with the following error :

03-21 19:20:56.455: E/AndroidRuntime(24471): FATAL EXCEPTION: main
03-21 19:20:56.455: E/AndroidRuntime(24471): java.lang.NoClassDefFoundError: org.acra.ACRA
03-21 19:20:56.455: E/AndroidRuntime(24471): at com.foxykeep.myproject.HomeActivity.onCreate(MyProjectApplication.java:127)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:969)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:3925)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.app.ActivityThread.access$1300(ActivityThread.java:122)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1184)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.os.Handler.dispatchMessage(Handler.java:99)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.os.Looper.loop(Looper.java:137)
03-21 19:20:56.455: E/AndroidRuntime(24471): at android.app.ActivityThread.main(ActivityThread.java:4340)
03-21 19:20:56.455: E/AndroidRuntime(24471): at java.lang.reflect.Method.invokeNative(Native Method)
03-21 19:20:56.455: E/AndroidRuntime(24471): at java.lang.reflect.Method.invoke(Method.java:511)
03-21 19:20:56.455: E/AndroidRuntime(24471): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
03-21 19:20:56.455: E/AndroidRuntime(24471): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
03-21 19:20:56.455: E/AndroidRuntime(24471): at dalvik.system.NativeStart.main(Native Method)

I asked Xavier Ducrohet and Tor Norbye (both working on the ADT plugin) on Google+ about the error and they linked to this page which explains how to solve the problem but is not ultra clear (at least for me).

So here is the same solution but explained with screenshots so we can see more easily what changed  :

From left to right, we have :

  • My project with ADT 16 (the small graphic changes are due to the fact the left image is from Eclipse Windows while the 2 on the right are from Eclipse Mac)
  • My project with ADT 17 before the fix
  • My project with ADT 17 after the fix
We can see :
  • In blue, the changes due to the patch (more info on this at the end of this post)
  • In red, the changes for the fix

What I did to fix the bug was :

  • Remove the libraries from the standard Java build path :
    • Right click on the project name > Properties > Java Build Path > tab Libraries > remove everything except the “Android X.X” (2.3.3 in my case) and the “Android Dependencies”
  • Rename the libraries folder from “lib” to “libs”
    • By doing that, all the libraries in the folder “libs” are found by the Android plugin and are added to the “Android Dependencies” item of the project
  • Clean the project (not exactly needed)
  • You are good to go !

This problem is due to the fact that libraries are not managed the same way with the new ADT build.
Instead of having 2 different managements of libraries (one for Android Library Projects, one for the standard jars), now both are merged into the Android Dependencies item.

Other info, the annotations.jar file is for the new lint Java annotations (more info here : http://tools.android.com/recent/ignoringlintwarnings)
Besides this small problem, the ADT version 17 looks really good and I love the new things added to the lint checks !!

[Update]

The DroidUX Team found another solution to fix the problem. I didn’t test it personally but it don’t see why it shouldn’t work : 

Hi,

Just to add to the solution:

If you have references to jars that are not in the ‘libs’ folder, for example if you use ‘classpath variable’, you can resolve the NoClassDefFoundError issue with the new ADT 17 by exporting the references.

To export the references: simply go to “Properties > Java Build Path > Order and Export”, and check all the references you want exported.

Cheers,

DroidUX Team

[/Update]

Page optimized by WP Minify WordPress Plugin