logo
Contact
facebook
linkedin
twitter
youtube
  • About
    • Careers
    • In the News
    • Leadership
    • About Float
  • Services
    • Getting Started
    • Assess Your Organization
    • Training and Education
    • Custom App Development
    • Consulting
  • Case Studies
  • Ideas and Insights
    • Research
    • Apps
    • Events
    • Book
    • Blog

Float provides mobile learning consulting, custom application development, and training and education.

Developing Better PhoneGap Apps

Developing Better PhoneGap Apps

Written by Daniel Pfeiffer
on March 9, 2011 | in Mobile Development, Tutorials | 33 Comments
Date: 09 Mar 2011
By: Daniel Pfeiffer
Tag: BlackBerry, Cross-Platform Toolkits, javascript, phonegap, tutorial, webOS
Comment: 33

A few days ago, Chad mentioned that creating a mobile experience for your users does not have to come at a steep cost if you take advantage of the mobile web. The devices that are on the market today have full-featured browsers—they fully support JavaScript and CSS. If you’ve started researching what it would take to get from mobile web to native app, you’ve likely come across PhoneGap—a framework allowing you to wrap a mobile web app and deploy it as if it were written natively.

We’ve done quite a bit of work in PhoneGap lately and the question inevitably comes up: are apps built in PhoneGap “slow”? A quick Google search returns a page full of reasons that would keep you from wanting to pursue using web technology in your next app. Before you write it off, however, we’ve come up with a few tips that have drastically improved our own PhoneGap apps.

Test on the Device From The Start

The simulators are great at determining whether snippets of code will work or not, but they do nothing to emulate the actual performance of the device. Take it from us: there is a huge difference. It would be worth your while to test on a handful of devices as older devices may see even further impacted performance. These older devices act as great lowest common denominators, don’t cost much used, and have an added benefit of helping to reduce ewaste.

Listen for ‘touchstart’ instead of ‘click’

This is probably the most simple, yet most noticeable change you can make. There is a ≈400ms delay between the user interaction and when the “click” event is fired. While listening for touchstart instead of click does nothing to boost the performance/efficiency of your code, it will make the app respond faster and perception is everything.

Equally important is a responsive UI. When the user taps something, the app should immediately respond in some way. In most cases, simply applying a class (e.g. “active” to highlight the tap) to the selected item is instantaneous, but we found a few cases where some devices would get hung up during a larger JavaScript project. A great way to ensure a responsive UI despite the occasional JavaScript hang up is by using the :active pseudo-class selector in CSS. This style is applied immediately to an element when a user has his or her finger on that element.

1
2
3
4
5
6
7
a:active,
a.active {
 background: #AAA;
}
element.addEventListener('touchstart',function(e) {
 e.currentTarget.className = "active";
});

See a Demo
(Check it out on your iOS, Android, or BlackBerry 6 device; BlackBerry OS 5 and older, Palm, Windows Mobile 7, and Nokia do not have the touchstart event as of writing this.)

A word of warning when listening for touchstart: You will need to strictly manage how the user can interact with the UI—this means disabling and enabling items when appropriate. Devices like the iPhone can detect more than one ‘touchstart’ event at the same time (try pressing the bottom two buttons in the demo at the same time, then try pressing the top two at the same time).

Update: In the comments, Ciaran made an excellent point that I totally forgot to address: how touchstart can cause problems when your application requires scrolling. When you touch the screen to scroll, it triggers a touchstart, producing undesired effects if you’re listening for touchstart for user interaction. We’ve worked around this by listening for a touchstart, touchmove, and a touchend. If the touchstart and touchend occurred without a touchmove, then it was a tap. It is reinventing the click event, but without the delay.

Update 2: Here’s a little demo of how we’re using touchstart, touchmove, and touchend to simulate a “tap” instead of relying on the click event. The item turns grey when its been touched, and red when it has been selected. Notice how Item 2 (the only item using the “click” listener) has a significant delay between being touched and selected. I commented the source code in the demo to give some insight behind the logic of the code. This was simply pulled out of one of our projects, so it’s not ready for primetime in your application—it’s meant to give you a push start on implementing it yourself.

See the Demo
(Only works on devices that implement touch events.)

CSS Animations

If you’ve tried to use JavaScript to animate anything in the DOM, you were likely sorely disappointed in the results using mobile devices. It’s not necessarily because the devices are slow to execute JavaScript (although that’s part of it), it’s the reflow and rendering of the DOM elements that causes a slow down.

CSS animations allow you to skip the reflow and render—they can immediately be painted in the window. The key in getting good CSS animations, however, are developing them to take advantage of the hardware acceleration when available (especially in iOS devices). Currently, only -webkit-transform and opacity offer hardware acceleration on iOS devices.

See the Demo
(Check it out on your iOS or Android device.)

The demo shows three blocks that all animate to the right, and then back. The first uses jQuery for animation, the second a CSS transition of the left property, and the third a CSS transition of the translate property. This is a simple example, but already you can notice the performance difference between using JavaScript and the hardware accelerated transform. It may seem like a small difference now, but the difference exponentially increases with every addition to the DOM and each added level of complexity to the animation.

A word of warning: flickering. Test your CSS transitions/animations early and often on the device. They tend to flicker in certain cases. We found the worst offender to be the iPad after using the keyboard or interacting with any input form element.

Optimize your JavaScript

Global Variables

Avoid global variables if at all possible. Besides being good practice, the garbage collector will not automatically clean up any global variables and you will eventually notice an impacted performance.

Memory Management

Help the garbage collector by marking variables for deletion. For example, if you have an initialization function that will only run once, at the end of the function, have it delete itself.

1
2
3
4
function init() {
  //application code here
  delete init;
}

Update: In the comments, Alex and Matt alerted me that I was misusing delete here. I had originally acquired this suggestion from the Apple JavaScript Developer Guide, but using delete is incorrect. delete is for releasing properties of objects and implicitly declared variables. While it seems like using delete actually works within Mobile Safari, it’s not the correct way to do it–and it’s not supposed to work according to the ECMA specification. The goal is to release variables (functions) from the global scope if they’re no longer needed. In this case, this init function likely is landing within the global scope. The best way is to not define this function in the global scope in the first place–JavaScript will automatically garbage collect variables that are no longer in scope. I believe it would also work to assign null to init when you’re done with the function.

JavaScript only has a function scope. Variables defined inside of blocks (e.g.: for loops or if statements) are not scoped to that block—they’re scoped to either the containing function or the global namespace. Try to reuse iterators or temporary variables.

Get into the practice of declaring all your variables with var. If you omit var while declaring variables within a function, the variable is assumed to be part of the global namespace and will not be marked for garbage collection upon return of the function.

Try a Different JavaScript Framework

If you’re using jQuery, it may be contributing significantly to the slowdown. jQuery includes a lot of compatibility for browsers like Internet Explorer 6. This is a lot of extra code being loaded into memory that you do not need. Get to know the devices you’re targeting—what kind of rendering engines are they using? iOS, Android, webOS, and BlackBerry 6 all use some version of WebKit, eliminating the need for (most) compatibility code within frameworks like jQuery. Two great alternatives are XUI or zepto.js. Both have a much smaller footprint (<10kb) and offer much of the same functionality as jQuery.

Concatenate and Minify

Combine all your JavaScript into one file and minify the file prior to distribution. This should improve the loading time of your app. No other resources can be loaded while JavaScript is being loaded and executed. By placing everything into one file, it keeps this pause to a minimum.

JSLint

Running your code through JSLint is a great way to quickly have a “second eye” look over your code and provide some optimization feedback.

Update: Since writing this, I’ve come to appreciate JSHint a lot more.

Update the OS

We’re referring specifically to iOS devices here. There were significant performance boosts in some of our apps when we upgraded our iPads from 3.2 to 4.2. The upcoming iOS 4.3 upgrade this Friday (3/11/2011) boasts up to twice as fast JavaScript execution using the Nitro Engine.

If you’re looking to release to the App Store, you may not have control over the iOS of your users (unless you specify that the app only works on 4.2 or later). But if you’re in an enterprise setting, you definitely have more control of the version of iOS running on the devices.

Split up the Work

PhoneGap provides a wonderful framework for integrating native code through the use of plugins. PhoneGap uses this for interfacing between the different device features such as the accelerometer or camera. However, it is also great for splitting up the work of some of the more processor intensive calculations in your application.

Find the processes which are taking the longest to complete and consider whether it would be worth your while to develop a plugin for this process (e.g. processing/resizing an image). Keep in mind that the plugin will have to be developed for every targeted platform.

Disable Multi-Tasking

Update: This used to make a bigger difference–there’s really no more benefit with the improvements in iOS 4.3 and iOS 5, as well as PhoneGap 1.0. However, we recommend making sure you stop timers when the app moves to the background otherwise they’ll queue up and when your app comes back to the foreground, they’ll all fire at once.

Another tip directed at iOS…and maybe a bit of a controversial one. We’ve found that PhoneGap apps left open in the background for too long don’t always resume as expected. Whether this is due to the fact that it had some memory taken from it while in the background we’ve yet to really explore. However, we’ve found that by preventing the app from resuming where it left off, there was improvement in the performance and reliability of the app. Of course, the huge downside is that the app starts over from the beginning every time (unless you implement your own resume functionality through cookies, for example). Users really expect their apps to pick up right where they left off so implement this tip with care. At the time of writing this, PhoneGap does not really communicate with the web application that it’s switching to the background or that it received a low memory warning in the background. (However, it looks like the upcoming 0.9.5 release of PhoneGap is hoping to include some of these features.)

Update: Use a Client-Side Templating Engine

One of the issues that significantly slows down the performance of an HTML-based app is the complexity of the DOM. (If you want a very technical explanation as to why, check out this post…best to read it after your morning cup of coffee, though). We explored a solution to this problem by using a client-side templating engine and saw significant improvements in our applications. By removing unused elements from the DOM, the time it took update existing elements significantly decreased and we saw an increase in speed and responsiveness in the application.

This is more than just a performance “tweak” however and really covers the overall structure of your application. Thus, it deserved it’s own post: How Well Does PhoneGap Scale?

Conclusion

You’ll notice that most of our tips are related specifically to the efficiency and quality of code in the mobile web app. Non-mobile browsers are usually a lot more lenient of poorly written code because they (often) have the memory and processing power to waste. You don’t have that luxury on a mobile device.

We’ve really enjoyed the flexibility that PhoneGap has allowed us to have while developing a few of our mobile applications. Not to mention the rapid development process from start to finish using web standards. These tips are only some of what we’ve found helpful during our development cycles.

Have you found any other helpful tips? Have any questions about other ways we optimized our PhoneGap apps? We’d love to hear how this has helped you while writing your PhoneGap app!

Are you interested in implementing a PhoneGap app for your organization?









Share this Article

Related posts:

  1. How Well Does PhoneGap Scale?
  2. Developing for Multiple Platforms
  3. Dan Pfeiffer: Developing for Multiple Platforms
  4. The Cost of Reflows on a Web App
Social Share
    33 Comments
    "Developing Better PhoneGap Apps"
    1. Ciaran March 10, 2011 at 11:46 am Reply
      The issue with using touchstart instead of onclick is that it can mess with the expected browsing experience, the main one being scrolling.If a user tries to scroll down the page but touches an element that fires a ontouch event, the page will automatically load the next page whereas an onclick event will cancel itself if it detects any movement, i.e. scrolling up/down/left/right.
    2. Daniel Pfeiffer March 10, 2011 at 12:55 pm Reply
      You make an excellent point, Ciaran. One of the big issues with touchstart right now is that every vendor is implementing them ever so slightly differently. If your application requires scrolling, using touchstart is going to produce unwanted effects.One solution that we've used is to listen for touchstart, touchmove, and touchend. If touchstart and touchend occurred without a touchmove, then it is a tap (and this actually more closely models how the standard iOS and Android interfaces respond). If there is a touchmove, then it was a scroll or a swipe or a cancel. It is kind of reinventing the "click" but without the delay.I neglected to mention that in my "word of warning." I'm going back and editing it now.Thanks!
    3. Ben March 11, 2011 at 9:30 am Reply
      Daniel, can you share the code you guys use to compare touchstart, touchend, and touchmove?
      • Daniel Pfeiffer March 11, 2011 at 12:29 pm Reply
        You got it, Ben! http://floatlearning.com/2011/03/developing-better-phonegap-apps/#update2
    4. jouni March 14, 2011 at 11:45 am Reply
      a workaround for the 400ms delay/scrolling is to use touchstart instead of onclick, but also check if the user is scrollinghttp://code.google.com/intl/ro-RO/mobile/articles/fast_buttons.html
    5. Xavier April 4, 2011 at 10:24 am Reply
      You say "Find the processes which are taking the longest to complete". I have not found a good technique yet within Phonegap to achieve this. Do you have any recommendations for profiling techniques within Phonegap applications?
      • Daniel Pfeiffer April 6, 2011 at 11:10 pm Reply
        Truthfully, it's been a mixture of guessing and checking by bookending segments of code in new Date() and comparing the before and after values.On a related note, a login form in one of my iPad apps was giving me a lot of grief with flickering. It seemed whenever the keyboard was used, all CSS transitions/animations started flickering. I ended up completely working around the problem by placing the login form in a modal view using a PhoneGap plugin to pass data back and forth.
    6. jussi April 5, 2011 at 4:10 pm Reply
      Is there a way to get responsive input for forms on initial selection, that brings up the keyboard? I tried using the above technique (as per links), but with both ios and android, it didn't seem to make much difference. On android the input selection was slightly faster, but on both systems the keyboard came up with a noticeable delay.
    7. SoFierce April 25, 2011 at 9:21 am Reply
      Thank You! Thank You! Thank You! Thank You! This is a really "snappy" solution to touch events. I almost abandoned my project until I found your tip (:active pseudo-class selector in CSS). I was getting painfully slow touchStart feedback. Now I'm back in action. Thanks again! Cheers.
    8. Eric Burnley July 14, 2011 at 3:18 pm Reply
      I know I'm a little late to the game here, but for click event replacement, I've been using the HTML5 Mobile Boilerplate Fast Button Click. It's basically the Google Fast Button click mentioned above, but accounts for scrolling and other things. I've put it in place on a mobile website, and it's much more responsive. Super easy to implement, thought it might help someone.Here is a test I did for work with several different touch events paired with touch styled media queries for moblie. Each subsequent screen (after clicking the next button) uses a different event, from click to touchstart to touchend to the fast button click.
    9. Srini September 12, 2011 at 11:59 am Reply
      Daniel,Great article!Do you know if apps built with phonegap can receive push notifications when they are in the background? The sample code from their wiki suggests that the app has to be running (foreground?) for it to receive push notifications.A second part this question is: if the app could receive remote notifications, can it send events to the elements on the page - e.g. refresh table entries?Thanks in advance!
      • Daniel Pfeiffer November 18, 2011 at 8:59 am Reply
        Hi Srini,There are two different types of notifications on iOS devices--push notifications (received from a remote server) and local notifications (scheduled by the app on the device). Both are attainable with PhoneGap:Local notifications: https://github.com/purplecabbage/phonegap-plugins/tree/master/iPhone/LocalNotification Push notifications: www.easyapns.comAs far as sending events to elements on the page, you cannot truly execute JavaScript or update the DOM while the app is in the background; changes will be queued and ran immediately upon resuming the app. But it sounds like what you want to do is for the app to receive a notification while it is in the background and have an updated view for the user when they return to the app. The easiest thing to do would be to update the table every time the app returns to the foreground. This way every time the app is opened (whether its from a notification or not) it will have the most up-to-date data.Hope that helps! Thanks, --Daniel
    10. Ari Lacenski October 19, 2011 at 9:32 pm Reply
      I've just started with Phonegap development, and I was advised to use jQuery Mobile, which of course relies on jQuery core. The reason given was that jQMobile does a good job of emulating native UI components. I haven't made the switch to xui because I'm developing by myself and don't have the resources to roll my own UI elements. Can you comment on the tradeoff and what one could do to make xui or zepto look more native? Many thanks.
      • Daniel Pfeiffer November 18, 2011 at 8:50 am Reply
        Hi Ari,jQuery Mobile and the jQuery library together (even when minified) come in at over 170kb--and that's just for the core for your application. We recently released a PhoneGap-based application to a client (a decently complicated application) where all the JS for the entire app only came in at 70kb (built on Zepto.js). It's not so much the size difference (because really, what's 100kb anymore) but it's what the size represents. A difference of 100kb of minified JS is a lot of code. The real problem is that of all this code being stored in your devices memory and taking away resources from the rest of your app--much of it will never be used (like all the jQuery code for IE6). There is a noticeable performance difference in apps of any substance or complexity. At this point in the game, I am not considering jQuery Mobile for use in any of my projects (although I still follow it).That being said, I understand your dilemma. jQuery Mobile has a very attractive feature--it helps you build the UI of your app so that it looks more like a native app. If that's a big need of yours, then you definitely don't want to write it off just yet. But there are alternatives: Have you looked at Jo (http://joapp.com/) or iUI (http://www.iui-js.org/)? Thomas Fuchs has a great resource for micro JS libraries as microjs.com--be sure to check it out!Thanks! --Daniel
    11. Don October 27, 2011 at 2:36 pm Reply
      How can u call an alert feature when an app is in the background
      • Daniel Pfeiffer November 18, 2011 at 8:54 am Reply
        Hi Don,I'm assuming you're talking about iOS devices. In this case, there are two different kinds of notifications: local notifications and push notifications. Both are very attainable with PhoneGap:Push notifications: http://www.easyapns.com/ Local notifications: https://github.com/purplecabbage/phonegap-plugins/tree/master/iPhone/LocalNotificationThere are likely more resources, these are just two I know about. Thanks! --Daniel
    12. laminina November 2, 2011 at 1:02 pm Reply
      Hi Daniel,Any best practices for the UI specifically for setting font size, image size...I'm using em's values and the same HTML/CSS files doesn't behave the same in iOS size and Android screen sizes? Any reason or am I missing something?Why 1em doesn't look the same in different screen sizes ...not even close! :p Thanks in advance for the help. :)
      • Daniel Pfeiffer November 18, 2011 at 9:01 am Reply
        Hi laminina,It sounds like you may have a misunderstanding about the way em sizes work--they are NOT an absolute measurement but a relative one.For example, if I set my body text to 12px and then set my header text (h1, for example) to 2em, it will appear as twice the size as my body text. What sounds like is happening is that you are using em sizes for all the text in your application so it is basing the relative size off the device's default stylesheet (which has different fonts and sizes between Android and iOS). Try using px measurements or, in your body tag, set the default font size to 14px to standardize the default size across the platforms.Hope that helps! --Daniel
    13. Daniel Pfeiffer November 18, 2011 at 9:06 am Reply
      I wrote a follow up to this post a few months ago that I believe a lot of people missed: http://floatlearning.com/2011/05/how-well-does-phonegap-scale/It discusses using a client-side templating engine to improve the performance of a PhoneGap application. We recently released a PhoneGap-based application to one of our clients that utilizes the approach outlined in this post and it saw noticeable performance improvements and benefits.
    14. Alex Grande December 12, 2011 at 6:11 pm Reply
      Thanks for this overview.One thing that you may want to modify in our article is that you shouldn't delete variables names.function init() { //application code here delete init; }... will result in a JSHint error because the delete keyword should be used on objects, not variables.
      • Daniel Pfeiffer January 19, 2012 at 10:47 am Reply
        Thanks for the heads up, Alex. I actually pulled that recommendation from the Apple JavaScript Development guidelines...but you are absolutely right. Deleting variables is (technically) not supposed to work. That's just for releasing objects.The goal is to release unneeded variables/functions from the global space (such as an initialization function).
    15. Anurag January 18, 2012 at 6:53 am Reply
      this is really nice matter given here specially for the beginners
    16. Matt January 28, 2012 at 2:47 pm Reply
      Actually the delete keyword in javascript is not for deleting objects, it is for deleting properties of objects. The reason this may seem to work is because you may be removing the property from the global object (window). But not all objects are deletable and you cannot delete a property that an object inherits from a prototype. The best you can do is remove as many references to an object as possible. Keep in mind that any closures referencing the object will keep it alive.More info: https://developer.mozilla.org/en/JavaScript/Reference/Operators/delete
      • Daniel Pfeiffer January 30, 2012 at 12:53 am Reply
        Thanks for the clarification, Matt. I'm fixing the article now. That silly Apple JavaScript Developer Guidelines got me all confused about this...apparently Apple got rid of that document since I wrote this article. Probably for the best...
    17. Laura May 27, 2012 at 4:30 pm Reply
      Thank you for this article!! This is exactly the info I need.
    18. Kishan August 11, 2012 at 5:51 am Reply
      I was about to start a phonegap project and came across this excellent article. Thank you for your time to research pro's && con's of each point on which a app relies.
    19. Gregor September 12, 2012 at 3:15 am Reply
      Great article. Very good job, helped me...thanks

    Pings

    1. Pingback: links for 2011-03-10 | Visualrinse | Design and Development by Chad Udell

    2. Pingback: links for 2011-03-10 ‹ The Iona Group

    3. Pingback: Mobile Phone Development » Blog Archive » HTML5 Bits and Pieces

    4. Pingback: 对学习移动应用开发的一点建议? | Flash开发者大会

    5. Pingback: Mobile Web开发笔记 | 山维空间

    6. Pingback: 75+ Mobile App Development Tutorials – Start Your App Success Today!

    Leave a Reply Cancel reply

    *
    *

    captcha *

    You may use these HTML tags and attributes:
    <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

    Subscribe to Our Blog

    Float Newsletter

    Float’s monthly newsletter offers industry news, tips and views on the mobile learning world. We promise only to send you the very best information, and we'll never sell or use your contact information to spam you.
    • Assess Readiness Float Mobile Learning

      Assess Your Organization

    • Float Mobile Learning Tin Can API

      Demo: Tin Can iOS Library

    • Tin-can-cartoon

      Tin Can in the Real World

    Twitter

    • @learnermobile Thanks for the #FF! about 1 day ago in reply to learnermobile
    • Inferring intent: how to leverage our smart, personalized devices for learning http://t.co/Fh6jz1eiFW about 1 day ago
    • @joansie74 You're welcome. Keep us posted on the book club! about 1 day ago in reply to joansie74
    • RT @joansie74: I will be starting a L+D book club,June 1st first on the list is #LearningEverywhere by Chad Udell #astd2013, please join th… about 1 day ago
    • RT @TrainingMagUS: #Mobile_Learning in Real Life: #mLearning Case Studies free webinar @scottfloat, - http://t.co/8VSpZSt8DZ about 2 days ago
    @floatlearning

    Most Popular Posts

    • Developing Better PhoneGap Apps
    • mLearning Is Not eLearning on A Mobile Device
    • Which Cross-Platform Framework is Right For Me?
    • Key Differences in Air for Android and iOS Packager App Capabilities
    • How Well Does PhoneGap Scale?
    • Instructional Design for Mobile Learning
    • Re-Signing an iOS App Without Xcode

    Categories

    • Comic
    • Conferences
    • Industry News
    • Mobile Apps
    • Mobile Development
    • Mobile Devices
    • Mobile Strategy
    • Newsletter
    • Pedagogy and Learning
    • Press
    • Research
    • The Six Ps
    • Tin Can
    • Tutorials
    • Uncategorized
    • User Experience
    • Webinars

    Archives

    • ► 2013
      • May
      • April
      • March
      • February
      • January
    • ► 2012
      • December
      • November
      • October
      • September
      • August
      • July
      • June
      • May
      • April
      • March
      • February
      • January
    • ► 2011
      • December
      • November
      • October
      • September
      • August
      • July
      • June
      • May
      • April
      • March
      • February
      • January
    • ► 2010
      • December
      • November
      • October
      • September
      • August
      • July
      • June
      • May
      • April
      • March

    Subscribe to the Float Mobile Learning Newsletter

    Monthly updates on the latest mobile learning trends, insights and analysis. Read the latest edition.

    phone: 309.263.2492 | Copyright © 2013 All rights reserved
    • About
    • Services
    • Case Studies
    • Ideas and Insights
    • Contact
    • Subscribe to Blog

    Most Popular Posts

    • Developing Better PhoneGap Apps
    • mLearning Is Not eLearning on A Mobile Device
    • Which Cross-Platform Framework is Right For Me?
    • Key Differences in Air for Android and iOS Packager App Capabilities
    • How Well Does PhoneGap Scale?

    Calendar of Posts

    March 2011
    MTWTFSS
    « Feb Apr »
     123456
    78910111213
    14151617181920
    21222324252627
    28293031 

    mLearning Links

    • ADL Mobile Learning Newsletter
    • eLearning Guild
    • eLearning on AllTop
    • mLearning on Twitter
    • mLearning Trends
    • mLearnopedia
    • Mobile Learning Blog
    • Mobile on AllTop
    • The Mobile Learning Revolution

    Other Client Work