jQuery Mobile Infinite Scroll

2

Infinite scroll is a useful feature that replaces pagination.  As the user scrolls down a list of items, the next page’s items are progressively loaded.  This technique is usually accomplished with an AJAX call and jQuery.  There are some well known plugins already available for this, most notable the one by Paul Irish, available here.

While working on a new project that involves jQuery Mobile and required infinite scroll, I hit a road block.  The existing infinite scroll plugins didn’t work nicely with JQM’s AJAX loader.  Since pages are loaded into the DOM, the plugins would get confused start loading elements for pages that were visible.  So, I created a simple plugin that was built just for JQM.

It’s not nearly as robust as the plugin mentioned above, but it does the job.  Just supply an ID for the link to the next page and a description of the elements to grab.  The plugin starts loading items before the user hits the bottom, which gives a true feeling of an infinite scroll (as long as the user can receive the data fast enough).  A link the GIT repo is available below.

https://github.com/kpheasey/jqm.infinitescroll

 

Easily Pass URL Parameters with jQuery Mobile

2

Passing URL paramters with jQuery Mobile can be a bit of a pain.  Especially if you want to load all your pages through AJAX.  After much trial and error I have pieced together a great function to grab valid URL parameters from every page in jQuery Mobile.

First, however, I would like to outline the basic framework of the application this function was designed around.  There are four basic types of pages; home page, object list page, object details page, and a global search page.  All pages are loaded via AJAX.

  • Home Page
    • Basic landing page with links and a search box that submits to the global search page
  • Object List Page
    • A searchable listing of objects with a  custom built infinite scroll feature.  Each list item is a link to the associated objects detail page.  Searching the list submits a form to the same page with a search parameter passed in the URL.
  • Object Detail Page
    • Displays the details of a specific database object instance.  The id is passed in the URL.
  • Global Search Page
    • Searches across all object lists to return the count of each type of object.  Clicking on an object count brings you to the object list page with the search parameter already filled out.

I’ve mentioned this just so you know the structure of the application.  I’m not guaranteeing the following will work on every application, but I don’t see why not.


function getUrlVars() {
      var vars = [], hash;
      var hashes;

      if($.mobile.activePage.data('url').indexOf("?") != -1){ // first check the active page url for parameters
           hashes = $.mobile.activePage.data('url').slice($.mobile.activePage.data('url').indexOf('?') + 1).split('&');
      } else { // otherwise just get the current url
           hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
      }

      for(var i = 0; i < hashes.length; i++) {
           hash = hashes[i].split('=');

           if(hash.length > 1 && hash[1].indexOf("#") == -1){ // filter out misinterpreted paramters caused by JQM url naming scheme
                vars.push(hash[0]);
                vars[hash[0]] = hash[1];
           }
      }

      return vars;
}

Sudo Code Breakdown

  • Find the URL
    • First check for a ‘?’ in the JQM active page URL
      • NOTE: since we are using AJAX to laod pages, the window.location URL is most likely the last page you were looking at and not the new request.
    • If it’s not there it just uses the normal window.location URL
  • Remove everything before the ‘?’ in the URL
  • Explode the remaining strings into segments with ‘&’ as the delimiter
  • Loop through the segments
    • Explode the segment using ‘=’ as the delimiter
    • If the exploded segment has more than one parts and the second segment does not contain a ‘#’, continue
      • If the segment contains ‘#’ it is not valid because it has been messed with by the JQM naming scheme, all paramters should come after the ‘#’
    • Save the segments as a parameter
  • Pass back parameters

In a production environment  this function has been performing flawlessly.  However, one improvement I have considered is to first chop off everything in the JQM active page URL before a ‘#’, then chop off everything before ‘?’.  In theory this should eliminate the need for the second conditional statement in the loop, as there shouldn’t be an instance of ‘#’.  As I said, this is working great and so I’m not tampering with it further.  If you do and it works or runs faster, let me know.

NetBeans OSX Lion Failed Installation Solution

0

There seems to be a common installation error when installing NetBeans on OSX Lion.  The problem is due to the fact that a Java Virtual Machine, JVM, does not actually come installed until you run a Java application.  So, the solution is to just run a Java application.  The easiest way is to search for “Java Preferences” in the finder and run that.  You will be prompted to install the JVM.  After that, rerun the NetBeans installation, just make sure you removed the NetBeans folder from you Application folder before reinstalling.

Converting ASHX to PDF

14

Here’s a quick little piece of information.  If you have a file with the .ashx file extension, just rename it to .pdf and you are good to go!  This is especially helpful for downloading documents from a ASP.NET site.

Javascript – onLoad Not Loading!

0

I recently ran into a problem with a piece of code that I have been using numerous times with no problems.  The code dynamically populates form options based on user input, the options are retrieved from the database as they are needed via an XMLHttpRequest.  To make the form sticky, this code would have to be run in the <body> onLoad event as well as when the form options had changed.  However, when the page loaded, the dynamic lists were not generated (even though this same process worked on countless other pages).

The source of the problem was that the form elements had not been loaded into the browser when the onLoad even was called.  This was most likely due to the size of the page.  The solution was to use the JavaScript onLoad event and place it at the bottom of the page, ensuring that all page elements had been loaded into the browser before the function was called.


<script language="javascript" type="text/javascript">

window.onload=function () {getAreas(state);};

</script>

I saw it recommended elsewhere that all onLoad functions be called from a master function. So, if I had 3 functions that I wanted to run onLoad, in my example, getAreas() would contain the 3 onLoad functions. Though you would probably want to rename it to something along the lines of loadFunctions().

Mapping PHP Objects & MySQL – A Simple Framework

1

If you’re a fan of Object Oriented Programming, you know how useful and time saving an object can be when developing software.  So, why not use them in PHP?

Generally, when working with PHP, the objects we want to represent are stored in a database.  This quick guide will show you how to implement an object in PHP that is linked to a MySQL database table.

First, I prefer not to represent the database itself as an object.  The reason for this is because it is much easier to handle the database as a function.  As shown in the previous post, I use dbConnect()/dbDisconnect() to connect and disconnect from the database.  So, for PHP/MySQL object tuorial, it is assumed that there is an open database connection.

Now for the code:

 class User {
 public $username;
 public $password;

public function __construct() {}

//Loads a user from the database by getting username and password.
 //Returns false if now username with matching password can be found.
 public function load($username, $password) {
 $query = mysql_query(“SELECT * FROM `users` WHERE `username` = '$username' AND `password` = MD5('$password')”);
 $result = mysql_fetch_object($query, get_class($this));

if (mysql_num_rows($query) == 1) {
 $this->username = $username;
 $this->password = $password;
 return true;
 } else {
 return false;
 }
 }

//Adds a user into the database
 public function create($username, $password) {
 $query = “INSERT INTO `users` (`username`, `password`) VALUES ('$username', MD5('$password'))”;
 $result = mysql_query($query) or die($query);
 $id = mysql_insert_id();
 $this->load($id);
 }

//Updates current user’s database information
 public function update() {
 $query = “UPDATE `obituaries` SET `username` = “ . $this->username . “, `password` = MD5(“ . $this->password . “) WHERE `username` = '“ . $this->username . “'“;
 mysql_query($query);
 }

//Deletes specified user from the database.
 static function delete($username) {
 $query = “DELETE FROM `users` WHERE `username` = `$username` LIMIT 1”;
 mysql_query($query);
 }

public function getUsername() {
 return $this->username;
 }

public function getPassword() {
 return $this->password;
 }

public function setUsername($username) {
 $this->username = $username;
 }

public function setPassword($password) {
 $this->password = $password;
 }

public function __destruct() {}
 }
 

Hopefully the comments in the code are enough explanation. If not, don’t be afraid to ask. Here’s a quick example of a login script using the User object.

 require_once('common/config.php');
 require_once('../class_lib/User.php');

dbConnect();
 session_start();

$user = new User();
 $username = secure($_POST['username']);
 $password = secure($_POST['password']);

if ($user->load($username, $password) !== false) {
 updateActivityLog($user->getUsername(), $_SERVER['REMOTE_ADDR']);
 $_SESSION['username'] = $user->getUsername();
 $_SESSION['password'] = $user->getpPassword();
 header(“location:dashboardManager”);
 } else {
 $_SESSION['message']['type'] = “errormsg”;
 $_SESSION['message']['message'] = “You have entered an invalid username and password”;
 header(“location:index.php”);
 }
 

Notice that after the MySQL database connection is established, a new User is created ($user).  Then $user is assigned the value of load($username, $password).  If this value is equivalent to false, the username/password was wrong and the user is alerted to their problem.

NOTE:
When dealing with a MySQL table with 20 columns, it is very useful to have a PHP object mapped to the database table in this way.  Another helpful hint when doing this is to name the PHP object fields the same as the MySQL table columns.  If you do this, the load() function can be simplified to:

 public function load($id) {
 $query = mysql_query(“SELECT * FROM `obituaries` WHERE `id` = $id”);
 $result = mysql_fetch_object($query, get_class($this));

foreach (get_object_vars($result) as $var => $value) {
 $this->$var = $value;
 }
 }
 

I hope you will find this framework a nice start for mapping a PHP object to a MySQL database table.  If you spot a problem or have a suggestion of your own, let us all know by leaving a comment.

Some Useful PHP Functions

1

Using functions in any language is always a smart idea. Not only does it save you from duplicating code, it makes your code more readable and easier to troubleshoot. If you have the same 3 lines of code being repeated 100 times, than you have to make 100 changes if the original 3 lines was wrong. If you had made those 3 lines into a function, it means that only one change would be needed. For this reasons, I have tons of functions that I use everyday, so I’m going to share a few with you.

First we have secure($input), this is a simply function to prevent SQL injection when working with MySQL. Some of you might say, but PHP has a function for this already, mysql_real_escape_string(). However, this can be easily bypassed by adding slashes in the appropriate areas. So, it is best to use stripslashes() and then mysql_real_escape_string(). For this reason, I condense these two function calls into secure($input).

function secure($input) {
     $input = stripslashes($input);
     $input = mysql_real_escape_string($input);

     return $input;
}

Next, we have enncrypt$data)/decrypt($data). These two functions do exactly what they say, they encrypt and decrypt a string. It is very important to be able to encrypt and decrypt confidential data in a web application. If someone actually performs SQL injection and gets the data, all they will get is a string of nonsense. The encryption technique used here is mcrypt, PHP has a function to do this already, but it is one function that handles both the encryption and decryption. I personally hate when a function does this, because the function then immediately breaks into an if/else structure based on the parameters; there should just be two functions instead of the one.

function encrypt($data) {
     $key = "YOUR KEY";

     $encrypted_data = mcrypt_ecb(MCRYPT_3DES, $key, $data, MCRYPT_ENCRYPT);

     return $encrypted_data;
}

function decrypt($data) {
     $key = "SAME KEY AS encrypt";

     $decrypted_data = mcrypt_ecb(MCRYPT_3DES, $key, $data, MCRYPT_DECRYPT);

     return $decrypted_data;
}

The next function is for formatting a date so that is can be stored in a MySQL database. This function is needed because PHP stores dates differently than MySQL. The function first converts the given date string to a time string. Then, it formats the time string as a date that can be recognized by MySQL.


function formatDate($date) {
     $date = strtotime($date);
     $date = date("Y-m-d", $date);

     return $date;
}

The next one is for connecting to a MySQL database. I created a function for this, because there are usually certain pages that doesn’t need to access the database and others that do. So, instead of creating a config file where the connection automatically takes place, I create a common function to connect to the database when necessary.

function dbConnect(){
     $hostname='localhost';
     $username='uname';
     $password='pword';
     $dbname='db';

     mysql_connect($hostname,$username, $password);
     mysql_select_db($dbname);
}

Finally, we have leaveOnlyNumbers(). This function takes a string as an input and outputs the same string with all characters except for numbers stripped from in. This is useful for formatting phone numbers, social security numbers, zip codes, etc.

function leaveOnlyNumbers($data){
     $data= ereg_replace("[^0-9]", "", $data);
     return $data;
}

I hope these functions are able to help someone write cleaner code. If not, I hope they will inspire you to write cleaner code. Let me know if you think any of these could be improved upon, or tell me about small custom functions you use frequently.

Great Application Template

1

If you’re like me you are not that artistic.  This means I’m a horrible designer.  Even though I know how to use all the tools, I can’t create a template from scratch.  That’s why I purchase templates for my applications.  Today, I just want to make a quick note of one of the best application templates I have found so far, Adminus.

This relatively inexpensive template is great for applications that don’t do 50 billion things.  I used to use SimplaAdmin, but have found that Adminus is easier to manipulate and has more functionality.  It has built in datepickers, file upload, charts and much more.

Preview of Adminus

The chart feature is one of the best.  It is incredibly simple to create a chart to show off whatever in an easy to read way.  My client’s love when I include a graph page to show subscriptions, page views, changes, etc.  As with most things we develop, they are easy to implement and people are amazed.

Adminus also sports a sexy liquid layout.  This means that you don’t need to worry about your site not displaying fully on someones ancient 1024×768 display.  All containers on the page are have their widths defined by percentage.

You can download the Adminus template at ThemeForest.

http://themeforest.net/item/adminus-beautiful-admin-panel-interface/94668

Upload, Analyze, and Import a CSV File With PHP and MySQL

66

Recently I completed a project in which I had to build an add-on for a marketing database application.  The add-on needed to upload a spreadsheet (I decided to use CSV as it is the easiest to work with), find all duplicates (in both the database and the spreadsheet itself) and then insert all unique entries into the database.  So, I stripped out all sensitive information and created these generic scripts to show you how to do it too.

There are two version of the script.   The first version is simple, it uploads the CSV file and then imports it to the database.  The other version displays analyzes the data from the file first and shows the user which entries are unique and which are duplicates, the user has to confirm this in order to import the data.

Uploading the file

First we need to get the file from the user.  In this case we will request the file through a simple HTML form, such as the one below.

<form enctype="multipart/form-data" action="importCSV.php" method="POST">
   <p>Choose a file to upload : <input name="file" type="file" /></p>
   <p><input type="submit" value="Upload File" /></p>
</form>

The file will be uploaded via the form and past to the importCSV.php script.  Now, we want to verify that the file being passed to the server is a CSV file.  If we don’t verify the file type, then the server might accept a file from a user with malicious intentions.


if($_FILES['file']['type'] != "application/vnd.ms-excel"){
   die("This is not a CSV file.");
}
elseif(is_uploaded_file($_FILES['file']['tmp_name'])){
   //Process 'file'
}
else{
   die("You shouldn't be here");
}

The first if statement checks to see that the file is a CSV file.  If it is not a CSV file, the process dies and reports an error.  The elseif statement then checks that the file was uploaded via HTTP POST, if so the file will be processed.  If the file was not uploaded via HTTP POST, then you will want to kill the process as you don’t know where the file came from.

NOTE: The name of the file input from the form is the same name as the $_FILES[''] variable.

Import the data

Now that the file has been uploaded and verified we want to import it into the database.  First, we need to connect to the database, then we need to get the information from the file and finally create an INSERT statement for each line of the CSV file.

//Connect to the database
$dbhost = 'localhost';
$dbuser = 'root';
$dbpass = 'password';
$dbname = 'petstore';
$link = mysql_connect($dbhost, $dbuser, $dbpass) or die('Error connecting to mysql server');
mysql_select_db($dbname);

//Process CSV file
$handle = fopen($_FILES['file']['tmp_name'], "r");
$data = fgetcsv($handle, 1000, ";"); //Remove if CSV file does not have column headings
while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
   $att0 = mysql_real_escape_string($data[0]);
   $att1 = mysql_real_escape_string($data[1]);
   $att2 = mysql_real_escape_string($data[2]);
   $att3 = mysql_real_escape_string($data[3]);

   $sql = "INSERT INTO `tbl_name` (
               `attribute0` ,
               `attribute1` ,
               `attribute2` ,
               `attribute3`
               )
               VALUES ('" . $att0 . "', '" . $att1 . "', '" . $att2 . "', '" . $att3 . "')";

   mysql_query($sql);
}

NOTE: You may need to change fgetcsv() parameters if you have a CSV file with lines longer than 1000 characters or if the value delimiter is not a semicolon.

NOTE: the first time fgetcsv() is called, the data is not used. This is because the first line of the file is the column headings. If your CSV file does not have column headings you should remove this line.

NOTE: The CSV file must be formatted identically every time in order for the script to be reusable.

Now to put the whole script together.


if($_FILES["file"]["type"] != "application/vnd.ms-excel"){
   die("This is not a CSV file.");
}
elseif(is_uploaded_file($_FILES['file']['tmp_name'])){
   //Connect to the database
   $dbhost = 'localhost';
   $dbuser = 'root';
   $dbpass = 'password';
   $dbname = 'petstore';
   $link = mysql_connect($dbhost, $dbuser, $dbpass) or die('Error connecting to mysql server');
   mysql_select_db($dbname);

   //Process the CSV file
   $handle = fopen($_FILES['file']['tmp_name'], "r");
   $data = fgetcsv($handle, 1000, ";"); //Remove if CSV file does not have column headings
   while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
      $att0 = mysql_real_escape_string($data[0]);
      $att1 = mysql_real_escape_string($data[1]);
      $att2 = mysql_real_escape_string($data[2]);
      $att3 = mysql_real_escape_string($data[3]);

      $sql = "INSERT INTO `tbl_name` (
                 `attribute0` ,
                 `attribute1` ,
                 `attribute2` ,
                 `attribute3`
                 )
                 VALUES ('" . $att0 . "', '" . $att1 . "', '" . $att2 . "', '" . $att3 . "')";

      mysql_query($sql);
   }
   mysql_close($link);
   echo "CSV file successfully imported.";
}
else{
   die("You shouldn't be here");
}

This simple upload script is also available for download here.

Analyze the data

Most of the time we will need to first analyze the imported data in order to find duplicates or errors in the data.  In order to do this we will have to first check each row, then report back to the user on all rows (indicating which rows have errors and which are good), then after user confirmation we will have to import the data.

First, I renamed the importCSV.php script to analyzeCSV.php.  Then, I changed the file upload form’s action to analyzeCSV.php.  Next, I removed the mysql_query() call and included a check to verify that the information is not already in the database.  If the entry is unique, the insert statement is added to a session variable which holds all unique inserts.  The script also adds each row of the CSV file to a table which indicates if the row is unique or not.

if($_FILES["file"]["type"] != "application/vnd.ms-excel"){
   die("This is not a CSV file.");
}
elseif(is_uploaded_file($_FILES['file']['tmp_name'])){
   session_start()
   //Connect to the database
   $dbhost = 'localhost';
   $dbuser = 'root';
   $dbpass = 'password';
   $dbname = 'petstore';
   $link = mysql_connect($dbhost, $dbuser, $dbpass) or die('Error connecting to mysql server');
   mysql_select_db($dbname);

   //Process the CSV file
   $findings = "
<form method=\"post\" action=\"importCSV.php\">
<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">
   <tr>
      <td>Result</td>
      <td>Attribute 0</td>
      <td>Attribute 1</td>
      <td>Attribute 2</td>
      <td>Attribute 3</td>
   </tr>";

   $handle = fopen($_FILES['file']['tmp_name'], "r");
   $data = fgetcsv($handle, 1000, ";"); //Remove if CSV file does not have column headings
   while (($data = fgetcsv($handle, 1000, ";")) !== FALSE) {
      $att0 = mysql_real_escape_string($data[0]);
      $att1 = mysql_real_escape_string($data[1]);
      $att2 = mysql_real_escape_string($data[2]);
      $att3 = mysql_real_escape_string($data[3]);

      //Check if row is in database already
      $sql = "SELECT *
              FROM `tbl_name`
              WHERE `attribute0` = '" . $att0 . "'";    //In this example attribute0 is the primary key
      $result = mysql_query($sql);
      $count = mysql_num_rows($result);
      if($count > 0){
         $findings = $findings . "
   <tr>
      <td bgcolor=\"#FF0000\">DB Duplicate</td>
      <td>" . $att0 . "</td>
      <td>" . $att1 . "</td>
      <td>" . $att2 . "</td>
      <td>" . $att3 . "</td>
   </tr>";
      }

    //Row is unique
    else{
       //Add INSERT statement to INSERT queue
       $_SESSION['insert'] = $_SESSION['insert'] . "INSERT INTO `tbl_name` (
                                                       `attribute0` ,
                                                       `attribute1` ,
                                                       `attribute2` ,
                                                       `attribute3`
                                                       )
                                                       VALUES ('" . $att0 . "', '" . $att1 . "', '" . $att2 . "', '" . $att3 . "');";

      //Add row for row to findings table and mark unique
      $findings = $findings . "
   <tr>
      <td bgcolor=\"#00FF00\">&nbsp;</td>
      <td>" . $att0 . "</td>
      <td>" . $att1 . "</td>
      <td>" . $att2 . "</td>
      <td>" . $att3 . "</td>
   </tr>";
      }
   }
   mysql_close($link);

   $findings = $findings . "
   <tr>
      <td colspan=\"5\"><div align=\"center\"><input type=\"submit\" value=\"Confirm\" /></div></td>
   </tr>
</table>
</form>";
   echo $findings;
}
else{
   die("You shouldn't be here");
}

However, when I built my add-on I found that the CSV files being uploaded contained duplicates inside of the file, so we need to check that each row that isn’t already in the database isn’t in the queue to be added to the database.

//Check if row is already in INSERT queue
 elseif(strpos($_SESSION['insert'], "'" . $att0 . "'") != false){
   $findings = $findings . "
 <tr>
    <td bgcolor=\"#FF0000\">File Duplicate</td>
    <td>" . $att0 . "</td>
    <td>" . $att1 . "</td>
    <td>" . $att2 . "</td>
    <td>" . $att3 . "</td>
    </tr>";
 }

Now that all entries have been checked, and the findings have been reported back to the user.  After user confirmation, the system must execute all the insert statements.  This is done in a new script called importCSV.php which looks like this:


session_start();

//Make sure the insert query queue has been initialized
if(!isset($_SESSION['insert'])){
   die("You shouldn't be here.");
}

//Connect to the database
$dbhost = 'localhost';
$dbuser = 'root';
$dbpass = 'password';
$dbname = 'petstore';
$link = mysql_connect($dbhost, $dbuser, $dbpass) or die('Error connecting to mysql server');
mysql_select_db($dbname);

$queries = explode(';', $_SESSION['insert']);

foreach($queries as $query){
   if($query != ""){
      mysql_query($query);
   }
}

mysql_close($link);

echo "Done";

The session variable ‘insert’ is the insert statement queue.  It is broken apart using explode() and the delimiter is a semicolon.  Each query is then executed and the system reports when done.

NOTE: The importCSV.php (in both the simple case and the analyze case) does not do any sort of error handling if a query fails to execute.

You can download upload, analyze, import script files here.

Conclusion

That’s it, you are free to download and use these scripts however you like.  Just remember to give credit where credit is due.

Once again here are the download links for the scripts:

Simple Upload and Import CSV

Upload, Analyze, and Import CSV

If you have any questions, please submit them as comments on this page.  This way other users can see your question and my answer.

Android Logo

Top 3 Essential Android Apps

1

I’m a big supporter of the Android mobile platform, mainly because it is open source and unlike the iPhone, there is no application approval process.  I’ve been slowly learning to develop for the platform by building my own game for the phone.  Currently, I have a Droid Eris, which is one of the slowest Android phones you can have.  Regardless of the speed of the phone, I still download a lot of apps and mess around with them.  So I’ve compiled a list of 3 essential apps that no one with an Android phone should be without.  These essential apps are not so much for fun, but rather they change the way you see and interact with your phone.

SwiftKey

Although this keyboard is still in beta it trounces the built in Android keyboard.  The keyboard doesn’t just look at what keys you might be trying to press, it also looks at the words you have written previously to predict what word you might want next.  After using the keyboard for more than a month now, I barely have to type full words anymore, or even start a word, this is because SwiftKey learns how you type in order to predict the next word even better. The application even keeps track of how many keystrokes it has saved you.SwiftKey QR Code

You can download the SwiftKey keyboard for free from the market by scanning the QR code to the right or using one of the links below.

Click here to download

Official SwiftKey site

ChompSMS

ChompSMSWhile the built in messaging service works great in Android, it lacks a few features and isn’t that visual appealing.  ChompSMS offers a few extras that put it above and beyond other messaging clients on Android.  First is the popup message window that shows you the text without having to actually enter the client, you can even reply to the text from here, or mark it unread for later.  The interface resemebles that of the iPhone’s messaging service, with talk bubbles appearing from alternate sides of the screen.  I’ve also noticed a performance increase since using this messaging service; I can load all my messages faster and it doesn’t lock up when I receive a phone call.

You can download ChompSMS for free from the market by scanning the QR code below or using one of the links below.  There is also an ad-free version available for a small fee

ChompSMS QR Code

Click here to download

Official ChompSMS site

LauncherPro

This is without a doubt my favorite app.  LauncherPro completely replaces your default user interface, in my case HTC Sense, with a sleek and fast new interface.  You can customize the amount of home screens from 1-9 and create a home row that is at the bottom of every screen.  The app includes nice sharp new icons for common tasks such as Gmail and your calendar.  This is one app that I can’t even begin to describe fully, you just have to check out the video and try it out yourself.

You can use the QR code below to download this free interface.  There is also a paid version that includes extra icons.

Official LauncherPro site

There are plenty of other great apps out there, but today I just felt like covering the essential apps.  In the future I’ll share some great money management, exercise, news, and game apps (hopefully my own too) with you

Go to Top