Antew

10Feb/130

Alert Dialogs – Part 1

This tutorial will walk you through creating a simple log out dialog that prompts the user for a decision, takes arguments, handles orientation changes, and communicates the decision to the containing Activity.

Filed under: General Continue reading
29Nov/120

Using Multiple Layouts in a ListView

Ever wanted multiple layouts in a ListView, perhaps a list of events with a header containing the date, and events for the day below? In this post I will show you one way to accomplish that. The full code for this project is available on Github. In the next post I'll show how to use the ViewHolder pattern to minimize findViewById(int) calls.

The final result

The keys to making this happen are Adapter.getItemViewType(int) and Adapter.getViewTypeCount()

getItemViewType should return an integer that identifies what type of View that will be created by getView(int, View, ViewGroup) for the specified item. Two views should return the same result if one can be converted to the other in getView

getViewTypeCount should return an integer with the number of types of Views that the Adapter will handle.

Behind the scenes Android uses the View type to determine what type of View should be passed in to the getView method. You can think of it as creating multiple buckets of Views that can be reused and thus avoid the penalty of inflating a new view each time a new list item becomes visible.

As the user scrolls down the ListView the framework will recycle rows that are no longer visible and add them to the appropriate bucket for reuse. When a new Header or Event item comes into view the framework will check to see if one of those views already exists in the bucket of recycled views, if so it will be passed in as the convertView to getView.

First, let's start with the Item interface that each row type will implement. getViewType will return the type of View for this row. getView will handle creating a new row or recycling an old one.

public interface Item {
    public int getViewType();
    public View getView(LayoutInflater inflater, View convertView);
}

Next, let's take a look at an Adapter implementation that takes a list of objects implementing Item. You don't have to use an enum, but I think it helps with code clarity here.

public class MyListAdapter extends ArrayAdapter<Item> {
    private List<Item> items;
    private LayoutInflater inflater;

    public enum RowType {
        // Here we have two items types, you can have as many as you like though
        LIST_ITEM, HEADER_ITEM
    }

    public MyListAdapter(Context context, LayoutInflater inflater, List<Item> items) {
        super(context, 0, items);
        this.items = items;
        this.inflater = inflater;
    }

    @Override
    public int getViewTypeCount() {
        // Get the number of items in the enum
        return RowType.values().length;

    }

    @Override
    public int getItemViewType(int position) {
        // Use getViewType from the Item interface
        return items.get(position).getViewType();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Use getView from the Item interface
        return items.get(position).getView(inflater, convertView);
    }
}

Now we need some Items that we can add to the Adapter. Let's begin with the Header type, which is just a LinearLayout containing a TextView.

public class Header implements Item {
    private final String         name;

    public Header(String name) {
        this.name = name;
    }

    @Override
    public int getViewType() {
        return RowType.HEADER_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        if (convertView == null) {
            // No views to reuse, need to inflate a new view
            convertView = (View) inflater.inflate(R.layout.header, null);
        }

        TextView text = (TextView) convertView.findViewById(R.id.headerText);
        text.setText(name);

        return convertView;
    }

}

Next we'll create the EventItem type, which consists of two TextViews in a LinearLayout and shares much of the same code with the Header item type above.

public class EventItem implements Item {
    private final String         str1;
    private final String         str2;

    public EventItem(String text1, String text2) {
        this.str1 = text1;
        this.str2 = text2;
    }

    @Override
    public int getViewType() {
        return RowType.LIST_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        if (convertView == null) {
            convertView = (View) inflater.inflate(R.layout.list_item, null);
        }

        TextView text1 = (TextView) convertView.findViewById(R.id.list_content1);
        TextView text2 = (TextView) convertView.findViewById(R.id.list_content2);
        text1.setText(str1);
        text2.setText(str2);

        return convertView;
    }

}

All that is really left is to create a ListActivity and add some items to our new adapter.

public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LayoutInflater inflater = LayoutInflater.from(this);
        
        List<Item> items = new ArrayList<Item>();
        items.add(new Header("Friday - November 30th 2012"));
        items.add(new EventItem("8:30am" , "Start work"));
        items.add(new EventItem("9:15am" , "Call Bob"));
        items.add(new EventItem("11:00am", "Meeting with Joe"));
        items.add(new EventItem("5:00pm" , "Freedom!"));
        
        items.add(new Header("Saturday - December 1st 2012"));
        items.add(new EventItem("8:30am" , "Keep sleeping"));
        items.add(new EventItem("10:00am", "Wake up"));
        items.add(new EventItem("11:00am", "Walk the dog"));
        items.add(new EventItem("6:00pm" , "Dinner at John's"));
        
        items.add(new Header("Sunday - December 2rd 2012"));
        items.add(new EventItem("8:30am" , "Keep sleeping"));
        items.add(new EventItem("10:00am", "Wake up"));
        items.add(new EventItem("11:00am", "Walk the dog"));
        items.add(new EventItem("6:00pm" , "Dinner at John's"));

        items.add(new Header("Monday - December 3rd 2012"));
        items.add(new EventItem("8:30am" , "Start work"));
        items.add(new EventItem("9:15am" , "Call Bob"));
        items.add(new EventItem("11:00am", "Meeting with Joe"));
        items.add(new EventItem("5:00pm" , "Freedom!"));
        
        MyListAdapter adapter = new MyListAdapter(this, inflater, items);
        setListAdapter(adapter);
    }

}

 

Filed under: Android, General No Comments
22Nov/120

Using BuildConfig.DEBUG in Android Maven projects

I was setting up a project to build with Maven and I had some code that relied on the BuildConfig.DEBUG setting to enable Strict Mode and logging, but when I was running the release build of my project I found that it wasn't setting BuildConfig.DEBUG to False.

After some searching I found that the ability to set BuildConfig.DEBUG to True was added in Android Maven Plugin 3.3.2 and updated in 3.4.0.

If you are using 3.3.2 then you need to set the android.release property as a System Property.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.0-alpha-2</version>
    <executions>
        <execution>
            <goals>
                <goal>set-system-properties</goal>
            </goals>
            <configuration>
                <properties>
                    <property>
                        <name>android.release</name>
                        <value>true</value>
                    </property>
                </properties>
            </configuration>
        </execution>
    </executions>
</plugin>

If you're using version 3.4.0 of the Android Maven Plugin it can be configured by a standard Maven property with <android.release>true</android.release> in the properties section.

<profile>
	<id>release</id>
	<activation>
		<property>
			<name>performRelease</name>
			<value>true</value>
		</property>
	</activation>
	<properties>
	    <android.release>true</android.release>
	</properties>
...
Filed under: General No Comments
21Nov/120

Android with Eclipse – Conversion to Dalvik format failed with error 2

Today I was playing around with eclipse.ini trying to speed it up a bit (using this StackOver post), but when I restarted Eclipse I got the dreaded "Conversion to Dalvik format failed with error 2" message when building an Android project.  I tried running a Clean, running "Fix Project Properties", and refreshing, but had no luck.

Eventually I found Issue #9883 which noted that removing "-XX:+AggressiveOpts" from eclipse.ini fixed the issue.  I updated the eclipse.ini and everything started building fine again!

As an aside, here is my eclipse.ini (Windows 7 64bit w/Java 7):

-startup
plugins/org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar
-vm
C:\Program Files\Java\jdk1.7.0\jre\bin\server\jvm.dll
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.200.v20120522-1813
-product
org.eclipse.epp.package.java.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
1024M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
1024m
--launcher.defaultAction
openFile
-vmargs
-server
-Xss500k
-Xms128m
-Xmx384m
-Xss4m
-XX:MaxPermHeapExpansion=10m
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+UseParNewGC
-XX:+CMSConcurrentMTEnabled
-XX:ConcGCThreads=2
-XX:ParallelGCThreads=2
-XX:+CMSIncrementalPacing
-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=5
-XX:GCTimeRatio=49
-XX:MaxGCPauseMillis=20
-XX:GCPauseIntervalMillis=1000
-XX:+UseCMSCompactAtFullCollection
-XX:+CMSClassUnloadingEnabled
-XX:+DoEscapeAnalysis
-XX:+UseCompressedOops
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
Tagged as: , No Comments
17Nov/120

AdMob – Adjusting AdView height when using SMART_BANNER

I recently ran into a situation where I needed to find the height of an AdMob ad and adjust another view based on its size. The problem with simply calling View.getHeight() is that it will return zero until the View is actually laid out on the screen, even in onResume() it will return zero.

The solution is to use an ViewTreeObserver.OnGlobalLayoutListener and listener for when the height of the view is non-zero.

mAdView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        int height = mAdView.getHeight();

        if (height > 0) {
            // Here I adjust the bottom margin of an ImageView
            RelativeLayout.LayoutParams imageParams = (RelativeLayout.LayoutParams) mImageView.getLayoutParams();
            imageParams.setMargins(0, 0, 0, height);
            mImageView.setLayoutParams(imageParams);

            // We can remove the GlobalLayoutListener now that we found the height
            // This is important because if we didn't remove the listener when we modified
            // the view it would cause an infinite loop
            mAdView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    }
});
Tagged as: , No Comments
3Nov/120

Custom error icon for EditText on Android

I've been working on updating my Reddit in Pictures app and while I was adding error checking to the dialogs I found I wasn't a fan of the default error indicator icon.

The default setError method uses the drawable com.android.internal.R.drawable.indicator_input_error which is a red circle with a transparent exclamation point.

I wasn't a fan of the transparency in the exclamation point so I whipped up one with a white background in Inkscape.

Here's what it looks like with the updated image:

Here is a zip file with the SVG and different sizes in PNG format (xhdpi, hdpi, mdpi, ldpi).

Custom error indicator images

24Jun/123

Reddit In Pictures

Reddit In Picture - Imgur Album

Reddit In Pictures - Imgur Album view

For the last two weeks I've been working on a new Android application in my spare time, an image browser for Reddit that I'm calling Reddit In Pictures

The main features:

  • Browse any subreddit
  • Swipe between images to go backwards or forwards (new images are loaded automatically)
  • Ads can be disabled.
  • Toggle Not-Safe-For-Work (Nsfw) images on/off
  • Supports Imgur URLs and albums
  • Sort by age and category (Hot, Top - All Time, etc)
  • View a post summary with title, upvotes, subreddit, comments, submitter
  • Pinch-to-zoom on any image
  • View animated Gifs
  • Share posts with other apps (e.g. Facebook)
  • View the full post on the Reddit Mobile site (or open it in another Reddit mobile app)
  • Images are cached to avoid repeated downloads, available cache size will vary by device.

    Get it on Google Play

I am trying something new with this app i'm calling "RateWare".  Ads can be disabled at any time via the settings menu, in exchange for disabling ads I ask that users rate the app in the Google Play Store.  It works on the honor system, you don't have to e-mail me for a key, all I'm really looking for here is more ratings and more reviews - whether they be positive or negative.  It's a bit of a hybrid between freemium and ad supported apps.

I tried to find other examples of RateWare on the market, but I was unsuccessful, if anyone has encountered other examples I'd love to hear of them.

My plan is to release the free version with the features above, and then a Pro version that includes additional features like:

  • Logging in
  • Loading subscribed reddits
  • Upvoting and downvoting posts
  • Saving posts
  • Saving images

I'm more a fan of the freemium revenue model over a free version with ads and a pro version that simply has ads removed, and I think users will prefer it as well.

The main problem I see with simply removing ads in a paid version is that it's a poor experience from the user's perspective.  They're paying to remove the annoying parts of app they're already using, and aside from the warm fuzzies from supporting a developer they get nothing else.  By removing ads and adding features the transaction feels much more even handed, I'm not paying solely to stop being annoyed, I'm getting more a better app as well.


Filed under: Android 3 Comments
17Jun/120

Diablo 3 Character Builder for Android

This one is a collaboration between my brother and I, it is called Diablo 3 Builder (for Android), and like it sounds, it is for creating character builds.  We released it under the developer moniker "We Make Stuff".

It features:
- Support for all classes and followers
- High quality images for all skills and runes
- Choose active skills and runes
- Choose passive skills
- Choose followers and their skills
- Save and Load builds
- Load builds from the Battle.net Skill Calculator, just click a link!
- Easily share your build with friends

This was a lot more involved than the last project and it made it quite a bit more fun.

My brother wrote a scraper for the D3 website to turn the information into JSON we could then include with the app, and then we both worked on the other components.

We used:

  • ViewPagerIndicator for the title effect on the ViewPager
  • Gson for parsing the JSON (had used it in other projects)
  • ActionBarSherlock for the ActionBar
  • Maven for builds
  • BitBucket w/git for version control

Some of the cool stuff:

  • Made the app accept battle.net Skill Calculator links, e.g. if you open http://us.battle.net/d3/en/calculator/barbarian#bcUQgj!YVg!aZZZZZ on your phone the app will show in the list to consume the link. Intent filters are awesome.
  • You can share builds from within the app in battle.net link format via Messaging and other apps.
  • Added Action Mode selection for clearing list items. This was more fun for me than really cool...but I've never been a fan of the popup context menus, so I decided to figure out how to use action modes like G-mail/Messaging.

The app is doing pretty well on the market so far IMO, we have around 5,000 downloads and only positive reviews. It was definitely fun to write and I learned quite a bit.

Filed under: Android No Comments
17Jun/120

Easy Tip and Split Calculator

I actually released this application on the Google Play Store a while back, but I neglected to post about it.

I had been talking about Android development since I bought my first Android phone, the OG Droid, but a few months ago I decided to challenge myself to actually release an app.

Tip Calculators are a dime a dozen in the Market, but most are poorly designed and require too much work for what should be a simple task.  My main goals were to make it easy to use and to learn more about Android development. The app shows:

  • Creating custom controls with custom attributes (a HorizontalNumberPicker)
  • Using ActionbarSherlock
  • Using Maven for builds
  • Handling currency with BigDecimal
  • Handling orientation changes
  • Using an input filters on a text field
  • Simple menu item in ActionBar
The application is open source and licensed under the DO WHAT THE HELL YOU WANT TO PUBLIC LICENSE, meaning you are free to do whatever you wish with the source code.

[1] Screenshot

[2] Market Link

[3] Source Code The app has no ads and requires no permissions.

9Jun/120

Checkable Menu Items on Android

I needed to create a checkable menu in an Android app recently, luckily the framework makes it nice and simple.

First, create an XML file under /res/menu/.  In this example I've named my menu "main.xml" and we have a menu item that opens a sub-menu when clicked.

android:checkableBehavior has three valid values:

  1. single - Only one item from the group can be checked (radio buttons)
  2. all - All items can be checked (checkboxes)
  3. none - No items are checkable

You can specify whether an item starts out checked with android:checked="[true/false]"

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@+id/change_category" android:showAsAction="withText" android:title="Category">
        <menu>
            <group android:id="@+id/categories" android:checkableBehavior="single">
			    <item android:id="@+id/category_hot"           android:title="Hot"/>
			    <item android:id="@+id/category_new"           android:title="New"/>
			    <item android:id="@+id/category_controversial" android:title="Controversial"/>
			    <item android:id="@+id/category_top"           android:title="Top"/>
		    </group>
        </menu>
    </item>

</menu>

Then in the Activity we override the OnCreateOptionsMenu method to inflate our menu and add it to the View.

public enum Category { HOT, NEW, RISING, CONTROVERSIAL, TOP; }
@Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        super.onCreateOptionsMenu(menu);
        // If you are using ActionBarSherlock, call getSupportMenuInflater() instead
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);

        MenuItem categoryHot = menu.findItem(R.id.category_hot);
        MenuItem categoryControversial = menu.findItem(R.id.category_controversial);
        MenuItem categoryNew = menu.findItem(R.id.category_new);
        MenuItem categoryTop = menu.findItem(R.id.category_top);

        switch (mCategory)
        {
            case CONTROVERSIAL: categoryControversial.setChecked(true); break;
            case HOT:           categoryHot.setChecked(true);           break;
            case NEW:           categoryNew.setChecked(true);           break;
            case TOP:           categoryTop.setChecked(true);           break;
        }

        return true;
    }

Next, we override onOptionsItemSelected to respond to clicks in the menu.

@Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        item.setChecked(true);

        switch (item.getItemId())
        {
            case R.id.category_controversial: mCategory = Category.CONTROVERSIAL; break;
            case R.id.category_hot:           mCategory = Category.HOT; break;
            case R.id.category_new:           mCategory = Category.NEW; break;
            case R.id.category_top:           mCategory = Category.TOP; break;
        }

        return super.onOptionsItemSelected(item);
    }

See the full source code for the project on Github: https://github.com/antew/android_checkable_menus

Filed under: Android No Comments