To browse this website, you should unconditionally agree to its terms and conditions. If not, please stop and leave now. And, why is this site black? Reading black text on a white background is like staring at a lit bulb at close quarters. This website uses soft light to display the text and turns off the background.

Site Logo

AndroidWithoutStupid Java Library for Android

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" messsage

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

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

And, here is the AndroidWithoutStupid version:

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:

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.

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.

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.

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 + " - " + 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.

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.

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.

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.

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 oArticleTitlesList = new ArrayList();
                final ArrayList oArticleUrlList = new ArrayList();
                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 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, where 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 info. 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 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.

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);
}

Public Domain - No license - Open Source

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.

Download JAR

The library JAR file contains the source code and JavaDocs.

Download androidwithoutstupid.jar
Size: 0.869 MB

You can also browse the docs online at
www.vsubhash.in/sss-dls/AndroidWithoutStupid/doc/index.html.

Note: In the frames version of the docs, links to developer.android.com will not open properly. You will need to open those links in a new window.

AndroidWithoutStupid source code is also available under source control.

On Other Websites