FontAwesome icons in Android's MapFragment

I spent some time trying to customize MapMarkers in Google Maps Android SDK, I hope this guide is useful if you're trying to integrate FontAwesome's icons in your app's map layout. Android Iconfiy is a great library that provides FontAwesome icons in Android, so we'll use that as our source of icons.

Google Map's custom markers needs a BitmapDescriptor:

// Specify an icon
Iconify.IconValue iv = Iconify.IconValue.fa_car;

// Get the bitmap descriptor, we need to build this yet
BitmapDescriptor customMarker = getCustomMarker(iv);

// Setup the marker
MarkerOptions marker = new MarkerOptions()  
                .position(new LatLng(latitude, longitude))
                .title("My title")
                .snippet("My snippet")
                .icon(customMarker)
                .draggable(false);

// Add it to your map instance
mMap.addMarker(marker);  

We can generate the BitmapDescriptor as follows:

public BitmapDescriptor getCustomMarker(final IconValue iconValue) {  
    IconDrawable id = new IconDrawable(getBaseContext(), iconValue).actionBarSize();
    Drawable d = id.getCurrent();
    Bitmap bm = Bitmap.createBitmap(id.getIntrinsicWidth(), id.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);

    d.draw(c);

    return BitmapDescriptorFactory.fromBitmap(bm);
}

This generate a stroked icon:

I would rather have the shape filled. This is somehow tricky since we need to override the IconDrawable's draw method:

public BitmapDescriptor getCustomMarker(IconValue iconValue) {  
    IconDrawable id = new IconDrawable(getBaseContext(), iconValue) {
         @Override
         public void draw(Canvas canvas) {
             // The TextPaint is defined in the constructor
            // but we override it here
            TextPaint paint = new TextPaint();
            paint.setTypeface(Iconify.getTypeface(getBaseContext()));
            paint.setStyle(Paint.Style.STROKE_AND_FILL);
            paint.setTextAlign(Paint.Align.CENTER);
            paint.setUnderlineText(false);

            // If you need a custom color specify it here
            paint.setColor(Color.BLACK);

            paint.setAntiAlias(true);
            paint.setTextSize(getBounds().height());
            Rect textBounds = new Rect();
            String textValue = String.valueOf(iconValue.character());
            paint.getTextBounds(textValue, 0, 1, textBounds);
            float textBottom = (getBounds().height() - textBounds.height()) / 2f + textBounds.height() - textBounds.bottom;
            canvas.drawText(textValue, getBounds().width() / 2f, textBottom, paint);
        }

    }.actionBarSize();
     Drawable d = id.getCurrent();
    Bitmap bm = Bitmap.createBitmap(id.getIntrinsicWidth(), id.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);

    d.draw(c);

    return BitmapDescriptorFactory.fromBitmap(bm);
}

This gives us a filled icon:

Dialogs and activity lifecycle

Here's a typical situation with Android:

  1. Make an HTTP request specifying a callback.
  2. When the callback is executed, show a dialog requesting user input.
  3. Continue with your app's flow based on that input.

Unfortunately, your activity may have been dismissed between steps 1 and 2. So when you actually try to show the dialog, you'll get a nice exception:

 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
 FragmentManager.java:1343 in "android.support.v4.app.FragmentManagerImpl.checkStateLoss"
FragmentManager.java:1361 in "android.support.v4.app.FragmentManagerImpl.enqueueAction"  
BackStackRecord.java:595 in "android.support.v4.app.BackStackRecord.commitInternal"  
BackStackRecord.java:574 in "android.support.v4.app.BackStackRecord.commit"  
DialogFragment.java:127 in "android.support.v4.app.DialogFragment.show"  

The context of the request is still valid, so the callback will be executed anyways. We should be using the DialogFragment's utilities to manage the dialog's lifecycle, but unfortunately that's available only for API level >= 11.

Another option is to check if the activity is paused and only show the dialog if it's not. A modular way to do that is to specify a base activity and make all your activities inherit from that one:

public class BaseFragmentActivity extends SherlockFragmentActivity {

    private boolean mIsPaused = false;

    protected boolean isPaused() {
        return mIsPaused;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIsPaused = false;
    }
    @Override
    protected void onPause() {
        super.onPause();
        mIsPaused = true;
    }
    @Override
    protected void onResume() {
        super.onResume();
        mIsPaused = false;
    }
    @Override
    protected void onStart() {
        super.onStart();
        mIsPaused = false;
    }

}

Then in our activity we can check if it is paused. If not, then show the dialog:

public class MyActivity extends BaseFragmentActivity {

    ...

    protected void someCallback() {
        if (!this.isPaused()) {
            dialog.show();
        }
    }

    ...
}

Trying to crash the activity

Now we would like to test if this is working. An easy way to pause your activity can be done calling a new activity or app with adb. For example, to open the browser you can run the following command from the terminal:

adb shell am start -a android.intent.action.VIEW -d 'http://www.google.com'  

References and inspiration

http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
http://stackoverflow.com/questions/3512198/need-command-line-to-start-web-browser-using-adb
http://stackoverflow.com/questions/10685460/how-to-determine-in-onpostexecute-that-activity-is-not-paused-now-or-destroyed