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.
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...");
}
};
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+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.
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";
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.");
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);
}
final Intent oUrlLaunchIntent = new Intent(Intent.ACTION_VIEW);
oink.showOptionsMenu(
"CodeProject RSS Feed",
oArticleTitlesList,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
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, 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.
="1.0"="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(
"Launch",
oSimilarIntentsInfo.mAppDrawableList,
oSimilarIntentsInfo.mAppNameList,
R.layout.app_menu_row_layout,
R.id.menu_option_icon,
R.id.menu_option_title,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
util.launchApp(oSimilarIntentsInfo.mPackageNameList.get(which));
oink.showMessage(oSimilarIntentsInfo.mPackageNameList.get(which));
}
},
true);
}
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.