Moving localization from arrays.xml to strings.xml

Android provides a quite comfortable way of defining localized String arrays via providing one or more arrays.xml in the res/values folder (values, values-de, values-fr, …) e.g.

<string-array name="status>    
    <item>Unknown</item>
    <item>None</item>
    <item>New</item>
    <item>In Progress</item>
    <item>Solved</item>
    <item>Closed</item>
    <item>Invalid</item>
    <item>Reopen</item>
    <item>Zombie</item>
</string-array>

We experienced some disadvantages doing so:

  • you have to maintain two or more arrays.xml, if you have a lot of languages that can be quite an effort (and memory overhead)
  • when adding, removing or changing the order you need to that in each arrays.xml which is quite error-prone
  • you have to maintain two files per language in your favoured localization tool

With one arrays.xml containing only links to the localized strings.xml you can

  • easily add or remove or change the order within an array in a single arrays.xml without the need to do that for all language arrays.xml
  • maintain only the strings.xml in your favoured localization tool (online or custom offline)

For moving the localization content from the arrays.xml to the strings.xml we wrote a little Python SCRIPT creating String keys (with optional prefix, e.g. list_) for all items within an array, replacing the string with the key and providing the keys in a new strings.xml, e.g.

<string-array name="status">;
    <item>@string/status_unknown</item>
    <item>@string/status_none</item>
    <item>@string/status_new</item>
    <item>@string/status_in_progress</item>
    <item>@string/status_solved</item>
    <item>@string/status_closed</item>
    <item>@string/status_invalid</item>
    <item>@string/status_reopen</item>
    <item>@string/status_zombie</item>
</string-array>

Conent of new strings.xml

    ...
    <string name="status_unknown">Unknown</string>
    <string name="status_none">None</string>
    ...

Usage example:

py replace-array-strings.py -i arrays.xml -p array_

Notes:

  • you have to handle special characters yourself if it is included in one of your strings since Android does not allow all characters within String keys (e.g. “-”, “(“, …)
  • it currently works only on one language, we try to that for all languages at once

 

Setting Text Selection and Cursor Position in UITextFields

Would’t it be wonderful to set the cursor position of a UITextField just like we are used to from UITextViews?

[textField setSelectedRange:NSMakeRange(0, 0)];

This actually works, unfortunately it is an undocumented API. However, UITextField conforms to the UITextInput protocol, offering a comprehensive API for selecting text. The following method can be used with a NSRange. If range.length==0, only the cursor is set to  range.location without selecting text.

- (void)selectTextInTextField:(UITextField *)textField range:(NSRange)range {
UITextPosition *from = [textField positionFromPosition:[textField beginningOfDocument] offset:range.location];
UITextPosition *to = [textField positionFromPosition:from offset:range.length];
[textField setSelectedTextRange:[textField textRangeFromPosition:from toPosition:to]];
}

Android WebView in Scrollview

Putting a scrolling WebView inside a ScrollView in Android won’t work out of the box.
The ScrollView will intercept the WebView’s swipe events, preventing it from scrolling.

Answer 2 to this stackoverflow question describes the solution:
http://stackoverflow.com/questions/13257990/android-webview-inside-scrollview-scrolls-only-scrollview

You have to subclass WebView with something like this:

package com.mypackage.common.custom.android.widgets

public class TouchyWebView extends WebView {

    public TouchyWebView(Context context) {
        super(context);
    }

    public TouchyWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchyWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        requestDisallowInterceptTouchEvent(true);
        return super.onTouchEvent(event);
    }
}

Conditional scroll in a UITableView

We recently had the problem, we needed to call some custom code, only when scrollToRowAtIndexPath:atScrollPosition: would actually scroll our tableView. Here is the solution:

UITableViewCell* cell = self.tableDataView.tableView.visibleCells[0];
NSIndexPath* path = [self.tableDataView.tableView indexPathForCell:cell];
if (path.section != i) {
[self.tableDataView.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:i]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
//your own done
}

Initialize a private class

If you ever what to instanciate a class on iOS that you know exists, let´s say it exists on MacOSX and you´ve seen in in a log. Or if you want to play with other private API here is how you do [[NSTextTab alloc] initWithType:0 location:224] without even knowing the NSTextTab class.

Class NSTextTabClass = NSClassFromString(@"NSTextTab");
id tab = [NSTextTabClass alloc];
id realTap;
SEL selector = @selector(initWithType:location:);
NSMethodSignature* signature = [tab methodSignatureForSelector:selector];
NSInvocation* invoke = [NSInvocation invocationWithMethodSignature:signature];
[invoke setSelector:selector];
NSInteger mode = 0;
[invoke setArgument:&mode atIndex:2];
CGFloat position = 224.0;
[invoke setArgument:&position atIndex:3];
[invoke setTarget:tab];
[invoke invokeWithTarget:tab];
[invoke getReturnValue:&realTap];

This should show you how to do these kinds of tasks with NSInvocation;

By the way, class-dump is a great tool to find out about private classes if you have the binaries.

Handling Android Lint warning: Potential leak when using setTag

With the newest preview version of Lint for the Android Development Tools (ADT 21) there is an important check for leaks which can result when using View#setTag(int, Object) and passing another view. From the Lint doc:

Prior to Android 4.0, the implementation of View.setTag(int, Object) would store the objects in a static map, where the values were strongly referenced. This means that if the object contains any references pointing back to the context, the context (which points to pretty much everything else) will leak. If you pass a view, the view provides a reference to the context that created it. Similarly, view holders typically contain a view, and cursors are sometimes also associated with views.

If you have the time to take a look at source code you can see that a static WeakHashMap named sTags is used to store the tags. While weak hashmaps are known to be difficult on Android (or generally Java) it’s especially dangerous in this case. If you add a view as tag, you add a reference to it’s Context and in particular to Themes and Resources which can block quite some memory. The magic of Context is summarized in this interesting talk.

So what’s the problem at all? To improve the performance if List rendering using Adapters each list cell remembers it’s View components by holding a reference to the Views. Additionally you might want to add some data to handle onClick events easily. This would look like the following using a CursorAdapter:

@Override
public void bindView(View view, Context context, Cursor cursor) {
    final ImageView imageView = (ImageView)view.getTag(R.id.icon);
    final TextView textView = (TextView)view.getTag(R.id.text);
    // ...
    imageView.setImageBitmap(someImage);
    textView.setText(someText);
    // ...
    view.setTag(someData);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup group) {
    final LayoutInflater inflater = LayoutInflater.from(context);
    final View view = inflater.inflate(R.layout.list_item, null);
    view.setTag(R.id.icon, view.findViewById(R.id.icon));
    view.setTag(R.id.text, view.findViewById(R.id.text));
    // ...
    return view;
}

So, this is bad for SDK<4.0 and should not be used, unfortunately no solution was provided. Removing the references is not an option since it would decrease the performance due to the usage of the expensive View#findViewById(int) every bindView by a handful of FPS depending on the complexity of your list cell.

One solution is to go back using the ViewHolder pattern (which you might have already heard of) plus an additional field for the data object. The normal View#setTag(Object) method is not evil because it stores the data as plain non-static member mTag.

private static final class ViewHolder {
    ImageView imageView;
    TextView textView;
    // ...
    Object data;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
    ViewHolder holder = (ViewHolder)view.getTag();
    if (holder == null) {
        holder = new ViewHolder();
        holder.imageView = view.findViewById(R.id.icon));
        holder.textView = view.findViewById(R.id.text));
        // ...
    }
    holder.imageView.setImageBitmap(someImage);
    holder.textView.setText(someText);
    //...
    holder.data = someData;
}

Instead of giving data the type Object you can be of course more specific or go Generics. You can also still use the evil View#setTag(int,Object) for your data object if they are not to big and you don’t have any fear that they are not garbage collected.

AlertDialog.Builder with wrong context “token null is not for an application”

If you ever get a

Unable to add window — token null is not for an application”

after creating a AlertDialog with the AlertDialog.Builder, it is because your context to use the Builder is not an Activity Context. You cannot use the getApplication() as context

AlertDialog.Builder builder = new AlertDialog.Builder(this);

only works with an Activity Context

There is several stackoverflow posts as well that discuss this problem in case you want to read on.

How to dispose a one-time system sound id

Apple suggests in one of their examples about AudioServicesPlaySystemSound to store the system sound id in a property.
This looks good at first, causes headache though if the class holding the property is a ViewController and the system sound id is created in viewDidLoad.
In that case, the system sound id needs to be disposed in viewDidUnload and in dealloc – but only once.
As there is no documented “invalid system sound id”, we can not store the fact that the id has been disposed in the property itself the way we would set a pointer to nil after release to indicate it is not referencing anything.
We must know in dealloc though, if the id has already been disposed in viewDidUnload.
So this would require an extra property/ivar indicating the validity of our system sound id property… quite ugly.
Here is a nice, block based solution for the case where you want to play the sound only once / play a different sound every time. When there is no need to keep the system sound id around:

  SystemSoundID systemSoundID;
  AudioServicesCreateSystemSoundID ((CFURLRef) url, &systemSoundID);
  AudioServicesPlaySystemSound(systemSoundID);
  // Sounds played with AudioServicesPlaySystemSound can not be longer then 30 secs.
  // So we can dispose the system sound id after that period.
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
    AudioServicesDisposeSystemSoundID(systemSoundID);
  });