Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Using AndroidWithoutStupid Java Library to Ease the Pain of Android Programming

4.79/5 (30 votes)
3 Feb 2016CPOL7 min read 43.3K   364  
One-line methods for your edification

Introduction

As someone who had done most of his coding in Visual Studio, I found it strange that Android Java required me to write a lot of what-should-have-been internal plumbing code. So, I started putting down one-line shortcut methods in a JAR file. This JAR grew over time and I decided to release it as a free public-domain software library on GitHub. I initially called it MvUtils but that was a stupid decision. I then renamed it as AndroidWithoutStupid, as that leaves nobody in doubt about its purpose.

Showing a "Hello World" Message

Here is the regular "toast" version of displaying a "Hello World" message in Android.

Java
Toast.makeText(
   getApplicationContext(), 
   "Hello, World!", 
   Toast.LENGTH_SHORT).show();

And, here is the AndroidWithoutStupid version:

Java
oink.showMessage("Hello, World!");

See how short that is? This is how you print messages in most languages, even in plain-old Java.

Okay. What is oink? Well, that is an instance of MvMessages class from AndroidWithoutStupid. As with other classes in the library, you will have to initialize an instance of MvMessages using the application context. So, I created oink with my current activity instance, which safely translates to the application context:

Java
MvMessages oink = new MvMessages(MyActivity.this);

Many classes in this library also have static methods that you can call without creating an instance.

So, what else can MvMessages do? Show prompts and notifications.

Showing a Yes-or-No Prompt

To show this kind of prompt, use your "oink" instance again and call its showPrompt() method. This returns a MvMessages.DialogInterfaceCaller instance on which you can load your Yes-click (onOkay) and No-click (onNotOkay) listener routines.

Java
DialogInterfaceCaller oPrompt =
        oink.showPrompt("File Download", "Do you want to download this file?");

oPrompt.onOkay =
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                oink.showMessage("Starting download...");
                // Other steps
            }
        };                

oPrompt.show();

Showing a Notification in the Status Bar

Notifications are a lot of work. With AndroidWithoutStupid, it is easy. As showing a notification requires an icon file, I have used the launcher icon here but you should use a smaller more suitable icon.

Java
oink.showNotification(
   "New Message",
   "5 unread e-mail messages ",
   "Check mail",
   R.drawable.ic_launcher);

MvMessages has several overloads of this method with which you can update notifications and also set a file or an app to be launched when the user presses a notification.

Show a List Menu

For showing a simple menu of choices, you need to use one of the many overloads of MvMessages.showMenu() method. As usual, the code is very simple.

Java
final String[] saOptions = { "Red", "Blue", "Green"};
oink.showOptionsMenu(
        "Select a color",
        saOptions,
        new DialogInterface.OnClickListener() {                            
            @Override
            public void onClick(DialogInterface dialog, int which) {
                oink.showMessage("You selected option " + (which+1) + " - " + saOptions[which]);
            }
        });

Calling this method immediately displays the menu. There is an overload of this method with a bAutoShow boolean argument that you can set to false to prevent the automatic display. The method returns a AlertDialog.Builder instance. You can call its show() method to display the menu.

Enough of interactive methods. Let's try something behind the scenes.

Downloading a File from the Web

As you know, performing network-related or other long-running tasks in your UI thread will tempt Android to kill your app. So, file downloads have to be done asynchronously. For this, AndroidWithoutStupid has a class named MvAsyncDownload. When you create an instance, it immediately starts downloading the file. MvAsyncDownload extends AsyncTask class and you need to keep track of the download by overriding AsyncTask implementation methods.

Java
MvAsyncDownload dl =
       new MvAsyncDownload(
          "http://www.example.com/",
          "/sdcard/index.html") {
      @Override
      protected void onPostExecute(MvException result) {
        if (result.mbSuccess) {
          oink.showMessage("Downloaded");
        } else {
          oink.showMessage("Failed " + result.msProblem );
        }
        super.onPostExecute(result);
      }
    };

In this code snippet, we try to download the home page of example.com (not the entire site, relax) and save it to a HTML file on your SD card ("external storage"). [This requires you to add Internet and storage write permissions to your app's manifest file. Without them, the code will not work.] Note how the onPostExecute method uses an MvException argument. This exception class is used by the library as the return value for many methods. This is against established wisdom of not taking exceptions too far away but I added it for the historical information it can provide (*cough* getMessage()). Also, note that for the sake of simplicity, I have hard-coded the destination path to which the HTML file is saved. This is a no-no. You should always query the external storage before doing that.

Java
if (MvFileIO.getExternalStoragePath().mbSuccess) {
    MvAsyncDownload dl =
        new MvAsyncDownload(
          "http://www.example.com/",
            MvFileIO.getExternalStoragePath().moResult.toString() + "/index.html") {
              @Override
                protected void onPostExecute(MvException result) {
                  if (result.mbSuccess) {
                      oink.showMessage("Downloaded");
                    } else {
                      oink.showMessage("Failed " + result.msProblem );
                    }
                  super.onPostExecute(result);
                }
          };    
}

The getExternalStoragePath() method is a bit outdated, as in newer versions of Android, there is built-in support for identifying multiple storage devices, not just the first one.

MvAsyncDownload has another constructor overload where it will guess the file name based on mimetype or the filename header sent by the webserver. This means you don't have to always have the destination file name on hand before downloading something.

In a service, as opposed to an activity, file downloads can be done or need to be done sequentially. An async task may be a wrong choice there. For such cases, you can use the static MvGeneral.startSyncDownload() method.

MvGeneral has several such methods - copy text to clipboard, launch apps by package name, play audio resources, etc.  Similarly, the MvFileIO class has methods to check for external storage, copy files, create directories, delete directories, check if a file or directory exists, save text to file, etc.

Parsing an RSS Feed

One of the apps that I created was a Web browser and I wanted to integrate an RSS/ATOM feed reader with it. RSS and ATOM feeds are XML files but the recommended classes for parsing XML files made my head spin. I had to then write my own feed parser. And, although RSS/ATOM feeds are XML files, they are almost always poorly generated by most websites. Most feeds use tags from multiple specs. Many employ custom tags. You need an extremely resilient parser to deal with them. If you try to go strictly by the spec, you will have to reject many feeds as invalid and your users will not like it.

To load data from an RSS or ATOM or RDF feed, create an instance of MvNewsFeed class with a file containing the downloaded feed. To ensure that the parsed feed is fully self-contained, pass the original URL of the feed to the constructor.

Java
if (MvFileIO.getExternalStoragePath().mbSuccess) {
  final String sFeedUrl =
      "http://www.codeproject.com/WebServices/ArticleRSS.aspx";
  final String sFeedFilePath =
      MvFileIO.getExternalStoragePath().moResult.toString() + "/codeproject_rss.xml";

    MvAsyncDownload dl =  

            new MvAsyncDownload(sFeedUrl, sFeedFilePath) {
            
            @Override
            protected void onPostExecute(MvException result) {
                if (result.mbSuccess) {
                MvNewsFeed oFeed = new MvNewsFeed(sFeedFilePath, sFeedUrl);
                if (oFeed.moMessages.size() > 0) {
                    oink.showMessage(
                        "The feed \"" + oFeed.msFeedTitle + "\" contains " +
                        oFeed.moMessages.size() + " articles.");
                } else {
                    oink.showMessage("Not a valid feed");
                }
              } else {
                    oink.showMessage("Failed " + result.msProblem );
                }
              super.onPostExecute(result);
            }            
        };    
}

After MvNewFeed instance has loaded the feed contents, they can be directly accessed from Java. For example, the feed title is available in the oFeed.msFeedTitle field. The articles in the feed are available in the oFeed.moMessages arraylist. This arraylist contains instances of MvNewsFeed.MvNewsFeedMessage class. An MvNewsFeedMessage instance holds the article title, URL, podcast link, etc.

Putting It All Together

Now, we will combine several of these classes and create an RSS feed reader for CodeProject.

Java
if (MvFileIO.getExternalStoragePath().mbSuccess) {

  final String sFeedUrl =
      "http://www.codeproject.com/WebServices/ArticleRSS.aspx";
  final String sFeedFilePath =
      MvFileIO.getExternalStoragePath().moResult.toString() + "/codeproject_rss.xml";
 
  // Download the feed to a file    
  MvAsyncDownload dl =  
    new MvAsyncDownload(sFeedUrl, sFeedFilePath) {
        
        @Override
        protected void onPostExecute(MvException result) {
            if (result.mbSuccess) {
              // After downloading, parse the feed file
            MvNewsFeed oFeed = new MvNewsFeed(sFeedFilePath, sFeedUrl);
            if (oFeed.moMessages.size() > 0) {
              // Show how many articles are available in the feed - better not
                oink.showMessage("The feed \"" + oFeed.msFeedTitle + "\" contains " +
                                 oFeed.moMessages.size() + " articles.");

                // Load the article titles and URLs in array lists
                ArrayList<String> oArticleTitlesList = new ArrayList<String>();
                final ArrayList<String> oArticleUrlList = new ArrayList<String>();
                for (int i =0; i < oFeed.moMessages.size(); i++) {
                    oArticleTitlesList.add(oFeed.moMessages.get(i).msMessageTitle);
                    oArticleUrlList.add(oFeed.moMessages.get(i).msMessageLink);
                }
                
                // Create an intent for launching the link of the
                // article selected by end-user
                final Intent oUrlLaunchIntent = new Intent(Intent.ACTION_VIEW);
                
                        // Show the article titles in a menu
                oink.showOptionsMenu(
                      "CodeProject RSS Feed",
                      oArticleTitlesList,
                      new DialogInterface.OnClickListener() {

                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                  // Launch the URL in a browser
                                  oUrlLaunchIntent.setData(Uri.parse(oArticleUrlList.get(which)));
                                  try {
                                      startActivity(oUrlLaunchIntent);
                                  } catch (Exception e) {
                                      oink.showMessage(
                                        "An error occurred when showing this article. -" + 
                                        e.getMessage());
                                  }
                                }
                                
                            }
                    );
            } else {
                oink.showMessage("Not a valid feed");
            }
          } else {
                oink.showMessage("Failed " + result.msProblem );
            }
          super.onPostExecute(result);
        }
  };    
}

More UI

An intent query where you can exclude a package helped build this 'Open in another browser' menu.An annoying problem in Android is that sometimes when you are trying to offload the work to another app, your own app will come up in the list of intents that can perform the job. So, I created a class called MvSimilarIntentsInfo where you can query intents but also explicitly exclude a particular package from the list. I used this class in my browser app for its "Open with" functionality. It allows the browser to open the current page in other installed browsers, wherein I exclude my own browser by specifying its package name.

This class provides a list of class names, app icons, and package names of the apps that can perform a job. With that information, it is easy to create an app launcher. And, MvMessages has a method just for that purpose - showOptionsMenuWithIcons. This method requires a layout XML file defining the appearance of a row in the menu. The layout should have an ImageView for the option icon and a TextView for the option label.

XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/black"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/menu_option_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|center_vertical"
        android:baselineAlignBottom="true"
        android:padding="5dip" />

    <TextView
        android:id="@+id/menu_option_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|center_vertical"
        android:background="@android:color/transparent"
        android:paddingLeft="5dip"
        android:paddingRight="5dip"
        android:textColor="@android:color/white"
        android:textSize="22dip" />

</LinearLayout>

I called this XML file app_menu_row_layout.xml and referred it in the showOptionsMenuWithIcons method.

Java
Intent oGenericLauncherIntent = new Intent(Intent.ACTION_MAIN);
oGenericLauncherIntent.addCategory(Intent.CATEGORY_LAUNCHER);

final MvGeneral util = new MvGeneral(MyActivity.this);        
final MvSimilarIntentsInfo oSimilarIntentsInfo =
        new MvSimilarIntentsInfo(MyActivity.this);

if (oSimilarIntentsInfo.getSimilarIntentsInfo(
                oGenericLauncherIntent,
                this.getPackageName()
        ).mbSuccess) {
    
    oink.showOptionsMenuWithIcons(
            // Menu title
            "Launch",
            // List of menu option icons
            oSimilarIntentsInfo.mAppDrawableList,
            // List of menu option text
            oSimilarIntentsInfo.mAppNameList,
            // Menu option layout
            R.layout.app_menu_row_layout,
            // ID of option icon's ImageView in the layout
            R.id.menu_option_icon,
            // ID of option icon's TextView in the layout
            R.id.menu_option_title,
            // Option click handler
            new DialogInterface.OnClickListener() {                        
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // Launch app by package name
                    util.launchApp(oSimilarIntentsInfo.mPackageNameList.get(which));
                    oink.showMessage(oSimilarIntentsInfo.mPackageNameList.get(which));
                }
            },
            true);
}

A menu with icons can be easily built with the showOptionsMenuWithIcons method.

Final Note

AndroidWithoutStupid has a lot more functionality than what has been mentioned here. I have published several apps using this library. They all work well. However, I am not a Java programmer by trade and so I welcome all improvements to the code on GitHub. The source code used in this article and the AndroidWithoutStupid JAR file is available at http://www.codeproject.com/KB/android/801927/CodeProject_AndroidWithoutStupid.zip

This library is released without copyright and is free public-domain software. It can be used in any kind of free or commercial software without attribution. You can use the library as is or cut-and-paste whatever code that you find useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)