Tuesday, September 21, 2010

Solution to: onContextItemSelected not called after selecting a context menu

Here is a solution to Android development problem, where the onContextItemSelected method is not called even after you see the context menu popup and selecting one of the items there.

1. Make sure that registerForContextMenu has been called, with the parameter the view you have the context menu on. For ListActivity with a context menu on the items, call registerForContextMenu(getListView()).

2. If you have an options menu on your activity (the one that pops up from the bottom of the screen when you press the hardware/softshell MENU button, you may be interrupting the event flow when you override onMenuItemSelected method. Overriding the onMenuItemSelected was taught in the Notepad v2 tutorial source code. That was partially wrong. You should not override it, instead you should override onOptionsItemSelected, and return false when the menu item is not handled. Example:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.menuImpor) {
        // do your work...
        return true;
    } else if (item.getItemId() == R.id.menuEkspor) {

        // do your work...
        return true;
    }
    return false; // no need to call super.onOptionsItemSelected(item)
}

That way, your onContextItemSelected should now be called correctly.

In a nutshell, "Menu" is the generic term for both "Context" and "Options" menu. Make sure you pay attention to the naming of the methods.

Tuesday, May 25, 2010

Setting value (object) while enumerating an NSMutableDictionary

Let's say you have an NSMutableDictionary instance which maps strings to numbers. For example, a list of stage names and the number of times the stage has been completed:

"level1" => 10
"level2" => 2
"final" => 1

I wanted to create a "clear data" function, so I enumerate the dictionary using the new foreach loop and set the value to 0 without adding or removing the key:

for (NSString *key in dictionary) {
    [dictionary setObject:[NSNumber numberWithInt:0] forKey:key];
}

This compiles fine, but a runtime error will occur:

Collection was mutated while being enumerated.

Unfortunately there is no API to modify the value even without adding or removing the key.

But, there is a solution: enumerate the keys instead of the dictionary. This works:

for (NSString *key in [dictionary allKeys]) {
    [dictionary setObject:[NSNumber numberWithInt:0] forKey:key];
}

Friday, April 9, 2010

Preventing TextView with links inside a ScrollView from dimming

I wrote an Android application that shows a scrollable TextView because I put it inside a ScrollView. When I added links to the TextView, I had to execute this so that the links can be clicked with proper highlighting:

textView.setLinksClickable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());

It works:

However, when I use my finger to scroll it, the text other than links are dimmed, very, very dark until we can almost see nothing.
Calling setClickable(false) and setLongClickable(false) fixed the issue, but the link itself is not highlighted anymore when "hovered", and the user may think that the link is not clickable.

I found a solution, which is not perfect, but works. Just set the color of the TextView. The links will stay on the same color, but the normal text will change color, and it does not dim anymore!

Here is what I get using #fff (a.k.a. 0xffffffff) color.

Tuesday, April 6, 2010

String.format in Android is extremely slow!

I'm used to using String.format to construct messages. Even when I don't need to specify width or number of decimal places. It just looks neater to the eyes.

For example, on the Alkitab (Bible) application, I wrote this for debugging:

Log.d("alki", String.format("tebakKitab fase 3: dengan %s:%d skor %d", ref.pendek, ref.pos, skor));

Each of "my operation", involving about 70 calls to the above line, takes about 300 ms. I thought that was acceptable.

But when I tried to build a game engine, I found a bottleneck somewhere that drags the fps. I found it to be the String.format call, which takes about 6ms for just one call! That's ridiculously slow.

Guess what: when I change the above line to:

Log.d("alki", "tebakKitab fase 3: dengan " + ref.pendek + ":" + ref.pos + " skor " + skor);

"my operation" takes only 22 ms!

Okay, once again: Don't use String.format in Android applications!

Note: When I traced method calls to know what makes it slow, String.format apparently calls a very deep code, something with DecimalFormatter, even CurrencyFormatter even though I didn't use it. It also calls com.ibm.icu.** packages. It really has a huge logic inside.

Thursday, December 17, 2009

Faster reading UTF-8 encoded file in Android

I created an Android application which reads some text files from a raw resource. The text files are encoded in UTF8. Therefore, I straight away wrote the code to convert bytes from the file into characters.

InputStreamReader in = new InputStreamReader(new BufferedInputStream(resources.openRawResource(R.raw.textfile)))
int c = in.read(); // read a character, and so on.

But, reading a 10KB file takes almost a minute on the Android 1.5 emulator! I wondered what made that so slow, in my Nokia phone, the same program written in Java ME takes less than a second to do the same thing.

By using Traceview, I found out that most of the time is spent on the UTF-8 decoding from bytes to characters. Android's Java implementation uses IBM ICU for character encoding. And it seems to be overkill to just decode UTF-8. Hence, the solution is to create own implementation if UTF-8 decoder. (Some concept taken from Go source, less the error-checking overhead and only look for max 16-bit characters.)

public class Utf8Reader implements Closeable {
    private InputStream in_;
    public static final char replacementChar = 0xFFFD;

    public Utf8Reader(InputStream in) {
        in_ = in;
    }

    public int read() throws IOException {
        int c0 = in_.read();

        if (c0 == -1) {
            // EOF
            return -1;
        }

        if (c0 < 0x80) {
            // input 1 byte, output 7 bit
            return c0;
        }

        int c1 = in_.read();

        if (c1 == -1) {
            // partial EOF
            return -1;
        }

        if (c0 < 0xe0) {
            // input 2 byte, output 5+6 = 11 bit
            return ((c0 & 0x1f) << 6) | (c1 & 0x3f);
        }

        int c2 = in_.read();

        if (c2 == -1) {
            // partial EOF
            return -1;
        }

        // input 3 byte, output 4+6+6 = 16 bit
        return ((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f);
    }

    @Override
    public void close() throws IOException {
        in_.close();
    }
}

(Please add the required import by yourself.) The result is satisfying: the 10KB file is now loaded in about 1 second in the emulator, and almost instantly on the device.

Monday, November 9, 2009

Base64 in PHP and Python

Today I calculated a hash value based on strings encoded with Base64 encoding.

One in PHP, and one in Python. Both of them should return the same value, because the hashes were compared for verification.

So, in PHP, the code is

myHashFunction(base64_encode('original string')) 

And in Python, the code is

myHashFunction(base64.encodestring('original string'))

Dangerous! The results are different! Since the 'original string' was not as simple as that, I thought I had passed the wrong data. But after some checking, the results of base64_encode and base64.encodestring were different.

base64_encode('original string') returns "b3JpZ2luYWwgc3RyaW5n"

whereas base64.encodestring('original string') returns "b3JpZ2luYWwgc3RyaW5n\n"

More precisely, base64.encodestring added new-line character at the end (and every 76 chars I think), suitable for email attachment, whereas base64_encode does not.

An easy solution to make them identical is to add replace function to the Python version, to become: base64.encodestring('original string').replace('\n', '').

Monday, October 12, 2009

find -exec equivalent for Windows cmd

I was looking for replacement of the shell (bash) command:
find -name '.svn' -exec rm -rf {} \;

for Windows cmd.exe.

The purpose is to remove all .svn directories from a directory recursively.

In cmd you can do dir /b /s to list directories in plain format including its subdirectories. For example:

C:\WINDOWS\system32\config>dir /s /b
C:\WINDOWS\system32\config\AppEvent.Evt
C:\WINDOWS\system32\config\system.sav
C:\WINDOWS\system32\config\systemprofile
C:\WINDOWS\system32\config\userdiff
C:\WINDOWS\system32\config\systemprofile\Desktop
C:\WINDOWS\system32\config\systemprofile\Favorites
C:\WINDOWS\system32\config\systemprofile\My Documents
C:\WINDOWS\system32\config\systemprofile\Start Menu

...

That format is already similar to what find command does. So how to execute a program with arguments taken from this list?

The answer is, use the FOR command with /F "usebackq" switch.

So, we put the command in backquotes, like this:

for /F "usebackq" %i in (`dir /s /b *.svn`) do rmdir /s /q %i

Problem solved. Remember to double the percent sign if you do this in a batch file.