Archive for the ‘Code’ Category

Implementing Custom Alert DialogFragments

Sunday, January 15th, 2012

So, I’ve been working on further revisions to my app. The most notable of these revisions involves switching action bar implementations from GreenDroid to ActionBarSherlock.  While this will benefit me tremendously as I’ll be able to use native Android 3.0+ components when running on those platforms, it means that I need to start using Fragments as well as the rest of the Android Support Package.

Refactor Refactor Refactor

I’ve be saving much of what I have learned for another post, but I felt like sharing this tidbit right now.  One thing that quickly became apparent was that a custom dialog that I was using (with positive/negative buttons) was not going to cut it, as the system dialogs were rendering the button strip differently…

The button strip isn't displayed correctly

This is implemented via…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
xml version="1.0" encoding="utf-8"?>
<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:minWidth="280dip"
 android:padding="10dip"
 android:id="@+id/layoutPageDialogRoot"
 >
  <EditText android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:id="@+id/editTextPageNumber"
   android:inputType="number" />
  <TextView android:id="@+id/textViewMaxPages"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" android:text=""
   android:gravity="right">
      <requestFocus></requestFocus>
  </TextView>
  <LinearLayout android:layout_height="wrap_content"
   android:id="@+id/linearLayout1"
   android:layout_width="fill_parent">
      <Button android:text="Go!"
        android:id="@+id/buttonOk"
        android:layout_height="wrap_content"
        android:layout_weight="1"
    android:layout_marginTop="3dip"
       android:layout_width="0dip" />
      <Button android:text="Nevermind"
        android:id="@+id/buttonCancel"
        android:layout_height="wrap_content"
    android:layout_weight="1"
    android:layout_marginTop="3dip"
       android:layout_width="0dip" />
  </LinearLayout>
</LinearLayout>

While this may seem like a minor UI issue at first, there’s something more to it – Android versions 3.0 and above switched the order of the default dialog button actions, placing the positive button on the right and the negative on the left.  The dialog above is using the pre-2.0 order.  When you’re dealing with muscle memory, such UX concerns become important (especially for deletion dialogs).

So, when refactoring the PaginationDialog into its own DialogFragment, I decided to do more than port my code.  I realized that the native AlertDialog class uses the operating system order, and is styled to match the OS version’s conventions. If only there was a way I could throw my layout into that… wait! There is – AlertDialog.Builder() has a setView() method!

By removing the buttons from the layout XML file and manually inflating it, I can stuff my Views into the Dialog and piggyback off its functionality.

My buttonless layout…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/layoutPageDialogRoot"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:minWidth="280dip"
   android:orientation="vertical"
   android:padding="10dip" >

    <EditText
       android:id="@+id/editTextPageNumber"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:inputType="number" />

    <TextView
       android:id="@+id/textViewMaxPages"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:gravity="right"
       android:text="" >

        <requestFocus>
        </requestFocus>
    </TextView>

</LinearLayout>

And the full DialogFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class PaginationDialogFragment extends DialogFragment {

    int currentPage, maxPages;

    static PaginationDialogFragment newInstance(int currentPage, int maxPages) {
        PaginationDialogFragment p = new PaginationDialogFragment();
        Bundle args = new Bundle();
        args.putInt("currentPage", currentPage);
        args.putInt("maxPages", maxPages);
        p.setArguments(args);
        return p;
    }

    /*
     * (non-Javadoc)
     *
     * @see android.support.v4.app.DialogFragment#onCreate(android.os.Bundle)
     */

    @Override
    public void onCreate(Bundle savedInstanceState) {
        currentPage = getArguments().getInt("currentPage");
        maxPages = getArguments().getInt("maxPages");
        super.onCreate(savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        final View v = inflater.inflate(R.layout.page_dialog, null);
        return new AlertDialog.Builder(getActivity())
                .setTitle("Go to page...")
                .setView(v)
                .setCancelable(true)
                .setPositiveButton("Ok!", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //validation code
                    }
                })
                .setNegativeButton("Aww, hell no!", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                        }
                    }).create();
    }
}

The important lines in the code snippet displayed above are 28, 29 and 32. On 28, the app gets the inflater being used by the Activity. Remember, you should never instantiate a LayoutInflater directly. After that, it’s pretty trivial to generate the dialog’s content (29) and then insert it into an AlertDialog‘s View hierarchy.

Run it and what do you get?

Beautiful!

Now, run it in Froyo to see if the buttons are automagically reordered…

And there was much rejoicing.

Disclaimer: Yes, I know I’m inlining strings that would be best stored in an external file. That’s a step I put off until I’m ready to make a release build.

Action Bar and You!

Saturday, August 13th, 2011

So I’ve been developing an Android application on and off for the past few months. In the latest iteration of the app, I wanted to make some options that were hidden in a menu more discoverable. The accepted way of doing this in Android is through adopting the Action Bar design pattern. As described in the official Android Developers Blog entry on the matter:

The Action bar gives your users onscreen access to the most frequently used actions in your application. We recommend you use this pattern if you want to dedicate screen real estate for common actions. Using this pattern replaces the title bar. It works with the Dashboard, as the upper left portion of the Action bar is where we recommend you place a quick link back to the dashboard or other app home screen.

Awesome, right? A unified UI paradigm for Android. Let me just add the widget to my Activity… umm, uhh… where is it? Nowhere in the Android 2.x SDKs. Instead, Google decided to kill two birds with one stone and encouraged developers to look at their source code for the Google I/O schedule app. This way people could not only learn how to use and implement the pattern, but they can also see what a well-written Android app is supposed to look like.

That’s all well and good, but what about the lazy efficient among us, who don’t want to reinvent the wheel and write the same instrumentation every time we make an Activity? Once again, Google to the rescue. With the advent of library projects, the ADT plugin for Eclipse makes importing standalone/reusable components into your existing project easy. After this feature was made available, the number of rich, third-party libraries for Android skyrocketed. Among these were several that had reusable Action Bar controls. After some experimentation, I settled on GreenDroid.

So, GreenDroid

GreenDroid’s claim to fame is the notion that you need to alter very little of your existing code to get attractive, functional UI components. Since my project was mostly complete at the time of implementation, this was a huge plus. And, for the most part, it ended up being true.
(more…)

File upload gotcha…

Sunday, April 3rd, 2011

I’m currently working on some much-needed updates to an AppEngine service that processes and serves content based on a CSV version of a spreadsheet.  One of these new features is the ability to upload the spreadsheet via an HTML form.  Of course, being early-ish on a Sunday morning, there was a little head scratching…

1
2
3
4
5
6
7
8
<html>
  <body>
    <form action="/admin" method="post">
      <div><input type="file" name="csvfile" accept="text/csv"></div>
      <div><input type="submit" value="Update CSV"></div>
    </form>
  </body>
</html>

Looks ok, right? Not quite. self.request.get("csvfile") only returns the file name! I must be forgetting something. After my coffee kicked in, I took another look at it and facepalmed. The following modification returned what I wanted.

1
<form action="/admin" enctype="multipart/form-data" method="post">

Sure enough, adding the “enctype” attribute has my code returning the contents of the CSV file.

Moral of the story? Don’t code before your morning intake of caffeine.