Monday, 18 February 2013

Overriding EditTextPreference

A common requirement for Android settings controls (and something that since ICS is now the preferred style according to the official Android design guide), is for the secondary text (also know as the 'summary text') below the preference name to show the current setting for that preference.

I have seen various solutions on the web for how to achieve this, many are complex and require custom handling code to be written for each preference. A neater and more reusable solution is to override the EditTextPreference class itself. There are various ways to do this to achieve the desired result, but I think overriding getSummary() is the neatest option.

public class MyTextPreference extends EditTextPreference {
    public MyTextPreference(Context context) {
        super(context);
    }

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

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

    // Override getSummary to return the current value formatted using the default summary string
    @Override
    public CharSequence getSummary() {
        return String.format(super.getSummary().toString(), getText());
    }
}

This solution allows you to specify a format string in the default summary string for example "Cache size is %s MB". The current preference value is then simply formatted using this template. If you want the current setting to be shown raw with no template, simply set the default summary text to "%s".

Thursday, 20 December 2012

The Mayans are coming, and bringing cheap IDE's!

A while I ago a raved about Intellij IDEA IDE, but complained about the somewhat over the top pricing of a personal licence. My feeling was that this was such a good IDE many hobbyists would be happy to pay say £50 for it, but the asking price of £175 was going to loose them a lot of sales.

Well thanks to the imminent Mayan apocalypse, it looks like they have seen the light and knocked 75% off the asking price until the world ends tomorrow!

Unsurprisingly to everyone it seems except IntelliJ, their website has been struggling to cope with demand ever since they announced the offer. My advice: get it while you can before the Mayans have the last laugh!

Sunday, 4 November 2012

Forcing Embedded tabs in ActionBar Sherlock

I recently spent some time trying to find a way to prevent Action Bar tabs from appearing on a second line below the Action Bar. I wanted them inlined in the Action Bar itself. Strangely, this is default behaviour in landscape mode but in portrait mode the tabs are always placed on a second line taking up valuable screen estate and frankly, for the small number of tabs I needed (just 2), looking ugly.

This behaviour occurs even when there is plenty of room for the tabs in the actionbar. There were various questions on Stack Overflow asking for solutions to this issue - including this one (EDIT: answer now added). I eventually found the answer buried in the answers to this rather unhelpfully titled question. I take no credit for this solution (I think 'hack' might be a better word!), It appears to originally come from poster 'Roberto' in this Google Groups thread. I have reproduced it here for reference.

//pre-ICS
if (actionBarSherlock instanceof ActionBarImpl) {
    enableEmbeddedTabs(actionBarSherlock);

//ICS and forward
} else if (actionBarSherlock instanceof ActionBarWrapper) {
    try {
        Field actionBarField = actionBarSherlock.getClass().getDeclaredField("mActionBar");
        actionBarField.setAccessible(true);
        enableEmbeddedTabs(actionBarField.get(actionBarSherlock));
    } catch (Exception e) {
        Log.e(TAG, "Error enabling embedded tabs", e);
    }
}

//helper method
private void enableEmbeddedTabs(Object actionBar) {
    try {
        Method setHasEmbeddedTabsMethod = actionBar.getClass().getDeclaredMethod("setHasEmbeddedTabs", boolean.class);
        setHasEmbeddedTabsMethod.setAccessible(true);
        setHasEmbeddedTabsMethod.invoke(actionBar, true);
    } catch (Exception e) {
        Log.e(TAG, "Error marking actionbar embedded", e);
    }
}
Sure it a bit of a gross hack using reflection, but if embedded tabs are allowed in landscape they really ought to be allowed in portrait too, provided there is space. The functionality is there so I can't see why Google (and hence ABS) have 'hidden' it.

Saturday, 20 October 2012

Intellij IDEA vs Eclipse

I have recently switched from Eclipse to Intellij IDEA for Android development. I finally got fed up with the horrendous lag and general sluggishness of Eclipse. An application that is essentially a glorified text editor should not lag for 2-3 seconds on a key press. Upgrading to the latest `juno` release was the final straw, not only was the lag still there but the ui had gone from being clunky but usable to completely illogical and infuriating. So I decided to give the Intellij IDEA community edition a go. Wow, what a breath of fresh air!

Very little lag, very smart auto complete functions, logical and fast ui. Even build and run seems significantly faster. Only problem is that the free version doesn't support Grails development, so i have to switch back to Eclipse for the server component of my project, which is now a painful experience. The full version of IDEA is an eye watering 175 GBP for personal users. I think they have really misjudged the pricing here, sure make commercial licences expensive, but they are pricing out a large segment of the amateur/hobbyist market. I'd happily pay 50 to 60 GBP for such a quality product, however 175 for what is at the end of the day just a glorified text editor is ridiculous.

Still, if i sell a few thousand copies of my app then I'll seriously think about treating myself...

Thursday, 18 October 2012

Recycling of views with Heterogeneous List Adapters

UPDATE: Issue now fixed in cleaner way, please see comments below.

I ran into an interesting 'quirk' or as I would call it a 'bug' in the way that Android recycles views in GridView (and presumably the same issue applies to ListView too).

Say you have an Adaptor which supplies two different views - one is a simple ImageView and one is a more complex layout inflated from xml. The android sdk docs and examples suggest that you can override getItemViewType() and getViewTypeCount() in order that android will sent the correct type of view to your getView() method for recycling. Like so:
private class ImageAdapter extends BaseAdapter {

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public int getItemViewType(int position) {
        return getDirItem(position).isDir()? TYPE_DIR : TYPE_PIC;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup container) {
        if (getItemViewType(position) == TYPE_DIR) { // Folder
            if (convertView == null) { 
                LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.folder_grid_entry, container, false);
            }

            convertView.setLayoutParams(mImageViewLayoutParams);
                
            // Populate view here ...            

            return convertView;
        } else { // Pic
            ImageView imageView;
            if (convertView == null) { 
                imageView = new ImageView(mContext);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            } else {
                imageView = (ImageView)convertView;
            }

            imageView.setLayoutParams(mImageViewLayoutParams);

            // load the image here (in a background thread) ...

            return imageView;
        }
    }

However, this doesn't work. Android will occasionally send the wrong type of view in the convertView parameter to getView() resulting in some strange errors. It seems that the whole view type business is no guarantee that getView() will receive the right view type, at best Android treats view type as a 'hint'.

To be fair the Android docs here do say:

"You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view."

However, in the very next sentence they go on to say:

"Heterogeneous lists can specify their number of view types, so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int))

It appears the the documentation is wrong in this regard, the view is not always of the right type and it is essential to check this like so:

public View getView(int position, View convertView, ViewGroup container) {
    if (getItemViewType(position) == TYPE_DIR) { // Folder
        if ((convertView == null) || (convertView.getClass() != LinearLayout.class)) { 
            LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(R.layout.folder_grid_entry, container, false);
        }

        convertView.setLayoutParams(mImageViewLayoutParams);
                
        // Populate view here ...            

        return convertView;
    } else { // Pic
        ImageView imageView;
        if ((convertView == null) || (convertView.getClass() != ImageView.class)) {
            imageView = new ImageView(mContext);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        } else {
            imageView = (ImageView)convertView;
        }

        imageView.setLayoutParams(mImageViewLayoutParams);

        // load the image here (in a background thread) ...

        return imageView;
    }
}

Tuesday, 16 October 2012

Installing ActionBarSherlock with IntelliJ IDEA

I recently decided to start using ActionBarSherlock (ABS) on my Android project and ran into a minor issue when trying to add ABS to my Intellij IDEA project.

I initially followed the instructions here, however it didn't work. It may be that this was written for an earlier version of ABS and/or IDEA, but I got errors because IDEA was trying to build the ABS tests and complaining about junit being missing.

The solution is to make sure that the the 'test' directory is unticked when adding ABS module to your project.

The other issue with the instructions at the above link is that it assumes you haven't already installed android.support.v4.jar. If you have then there can be an issue if the version supplied with ABS is different to the version you already have installed. I replaced the version supplied with ABS with the latest version and made sure the 'export' checkbox in the dependancies tab (Project Structure->Modules) of ABS module was checked, then removed the android.support.v4.jar from my app module. This makes sure that both ABS and your app use the same support jar. You can then safely ignore the second part of the linked instructions.

For the record, this applies to ABS version 4.2 and Intellij IDEA 11.1.