Posted on by

Now that we understand how Corona can fill out different screens and be responsive to the various shapes (see last week’s tutorial), we can use some of that technology to help your app be responsive to various devices and adjust for such things as device-specific fonts and the Kindle Fire soft button bar.

Corona provides us API calls to get information about the device we are using, and you can determine quite a bit from these calls.  One “guiding principle” for developers is DRY or Don’t Repeat Yourself.  Let’s look at an example:

if ( system.getInfo("model") == "iPad"   or
     system.getInfo("model") == "iPhone" or
     system.getInfo("model") == "iPod" ) then

     store.init( "apple", transactionCallback )
     store.restore()

else
   -- IAP isn't yet supported on Amazon or Barnes and Noble so don't start it up.
   if ( system.getInfo("model") ~= "Kindle Fire" or system.getInfo("model") ~= "Nook" ) then
      store.init( "google", transactionCallback )
      store.restore()
   end

end

---------- OR ----------

local kindlePadding = 0

if ( system.getInfo("model") == "Kindle Fire" or system.getInfo("model") == "KFJWI" ) then
   kindlePadding = 20
end
healthBar.y = healthBar.y - kindlePadding

If you write these system.getInfo(“model”) tests all over the place, you’ll end up writing a lot of code.  To compound matters, as Amazon and Barnes & Noble release new devices, the amount of potential model names increases.  There are currently five different Kindle Fire names to test against and four Nook models!

To solve this, you can create a very simple external module that will create an object that has a bunch of true/false settings, so this code can be reduced to:

if ( device.is_iPad ) then
   ...
end

---------- OR ----------

if ( device.isKindleFire ) then
   ...
end

At the end of the day, this is about saving you a few keystrokes of typing, but when we start working with the three different Android based platforms, it’s nice to know whether you’re on a regular Android device or on a modified Android platform like Nook or Kindle.

Assembling the Module

Let’s build our module and call it device.lua.  We are going to use the new module method and avoid the depreciated “module(…, package.seeall)” method.  Start with this block of code:

-- Create a table that will contain all of our tests we are setting up.
local M = {}

-- Set up some defaults...
M.isApple = false
M.isAndroid = false
M.isGoogle = false
M.isKindleFire = false
M.isNook = false
M.is_iPad = false
M.isTall = false
M.isSimulator = false

These are the values you will have to work with, and we will assume all of them to be false by default.  Now let’s grab our model name and store it temporarily to determine if we are on a device or the Corona Simulator:

local model = system.getInfo("model")

-- Are we on the Simulator?
if ( "simulator" == system.getInfo("environment") ) then
    M.isSimulator = true
end

When the iPhone 5 came out, the concept of knowing if you are on a “tall” device came into focus.  However, the iPhone 5 wasn’t the first 16:9 HDTV shaped screen, so we should retrieve this information for the 16:9 Android devices too.  In fact, what do you consider a “tall” device?  Do the 7″ tablets that are somewhere between the iPhone 4S and the iPhone 5 fall into this classification?  For the purpose of this tutorial, we will assume that anything taller than an iPhone 3/4 will qualify.  These 320×480 devices have an aspect ratio of 1.5:1 (480/320=1.5), so if we are taller than that orientation, let’s add it to the list.

if ( (display.pixelHeight/display.pixelWidth) > 1.5 ) then
   M.isTall = true
end

-- Now identify the Apple family of devices:
if ( string.sub( model, 1, 2 ) == "iP" ) then 
   -- We are an iOS device of some sort
   M.isApple = true

   if ( string.sub( model, 1, 4 ) == "iPad" ) then
      M.is_iPad = true
   end

else --...(the rest of the else is below)

At this time, every Apple model starts with iP, for example, iPod, iPad, or iPhone.  Thus, we can look at the first two letters of the model and if it’s iP, we know that we are running iOS and we set the isApple flag to true.  We also need to perform a quick sub-test to determine if we’re using an iPad or not, because it might be necessary to use an alternate layout for that screen shape.

Android makes our life challenging because there are three main marketplaces: Google Play, Amazon and Barnes & Noble.  Each of them have rules that you must follow, like “no ads” on Barnes & Noble and “no links to Google Play” from Amazon-hosted apps.  Since there is no simple way to say “use Google Play”, we have to eliminate the other stores first.

As mentioned above, there are multiple models for the Kindle and Nook devices which increases the challenge in knowing what to do.  Let’s look at the full else clause now:

else
   -- Not Apple, so it must be one of the Android devices
   M.isAndroid = true

   -- Let's assume we are on Google Play for the moment
   M.isGoogle = true

   -- All of the Kindles start with "K", although Corona builds before #976 returned
   -- "WFJWI" instead of "KFJWI" (this is now fixed, and our clause handles it regardless)
   if ( model == "Kindle Fire" or model == "WFJWI" or string.sub( model, 1, 2 ) == "KF" ) then
      M.isKindleFire = true
      M.isGoogle = false  --revert Google Play to false
   end

   -- Are we on a Nook?
   if ( string.sub( model, 1 ,4 ) == "Nook") or string.sub( model, 1, 4 ) == "BNRV" ) then
      M.isNook = true
      M.isGoogle = false  --revert Google Play to false
   end

end

Inspect the code above and follow along. Since we test positive for being an Apple device in the initial if part of the test, we must be on Android when we get to the else clause.  So, we start by assuming it’s Android and set the isGoogle flag to true.

Next, we test for the other two marketplaces in two separate steps. First, we test the model to see if it’s Kindle Fire, it starts with KF, or it’s WFJWI.  When Amazon released the four new Kindle Fire models, they gave them lovely model names like KFJWI.  The original Kindle Fire identifies as Kindle Fire.  Its 2nd-generation is KFOT.  The 7″ HD model is KFTT, the 9″ model is either KFJWI or KFJWA depending if it has WiFi or WAn access (3G). Luckily, we can simply check for KF or Kindle Fire to cover all models.  Are you tempted to just check for K?  That might work, but with the zillion Android models out there, there’s too much risk for a “false positive” — an Android model that returns K as its first letter, but it isn’t a Kindle Fire.  In summary, if we think we’re using a Kindle Fire, set isGoogle to false and set isKindleFire to true.

Finally we have to check for the Nook.  Barnes & Noble, for the most part, uses the string BNRV at the beginning of their model names, although the Nook Color might return Nook Color.

The last step to finish this up is to return our table of information to the app:

-- Return the table "M", providing access to it from where the module is "require"d
return M

Putting it to Work

To use this in your app, simply require the module at the beginning of your project:

local device = require("device")

Then when  you need to block off a block of code:

if ( device.isApple ) then
   store.init( "apple", transactionCallback )
   store.restore()

elseif ( device.isGoogle ) then
   store.init( "google", transactionCallback )
   store.restore()

end

This provides a much cleaner way of doing device specific code — and you can never be “too careful” about this when you’re developing for multiple devices and multiple OS versions. Furthermore, by building this detection into an external module, it’s easy for us to modify the logical flow as new devices with new model names and varying capabilities enter the consumer market. You can get started by downloading the entire module here.

Pair this module with my Ultimate config.lua and you have a formidable setup that will prepare your build environment for virtually all devices currently in the market, and adapt for future devices too!


Posted by . Thanks for reading...

13 Responses to “Device Detection”

  1. Tim

    Rob – another great tutorial! Good Work.

    The biggest challenge left is asset management. It would be great to have build variables (build scripts) that could exclude assets or what not because it can be challenging to contain your app footprint when building for so many devices.

    Trying to stay under the 50MB limit on Android with so many different assets for the various sizes and platforms has been one of my biggest issues. It would also be nice to have a switch to have the build script precompile/compress the PNG Files for apple before connecting to Corona to do your build as this step can cause your builds to take a very long time.

    Anyway, thanks again for the thoughtful posts and if you have any ideas on build targets and asset management I would love to hear them.

    Reply
  2. Andrew

    Great tutorial. Thus week less to very organized code and improve adaptability when new models become available! Thanks!

    Reply
  3. Luciane

    Hi Rob,
    another very useful tutorial!
    For Nook device names is it: BNRV(in your write-up) or BRNV (line 16 in the code above)?
    Thanks, Luciane

    Reply
  4. thegdog

    Great post, Rob.

    There is a parenthetical issue in device-detect-3.lua, however. I assume that you didn’t want to have the closing parentheses after “Nook”.

    Should be:
    if ( string.sub( model, 1 ,4 ) == “Nook” or string.sub( model, 1, 4 ) == “BNRV” ) then

    Reply
  5. Jeremy

    Rob,

    Nice article. Device makes for a very helpful utility class.

    I’m using this to attempt to disable Vibration on Nook devices since it’s unavailable. It works fine in the simulator but my app kept getting kicked back by B&N. I broke down and bought a Nook HD 7″ and it reports its model as “BNTV400″.

    The following line should probably be updated to include this new string.

    if ( string.sub( model, 1 ,4 ) == “Nook”) or string.sub( model, 1, 4 ) == “BNRV” ) then

    end

    Reply
  6. Fritz

    One other suggestion from experience on this topic… I never actually use “if is this device” in my code. Instead, I use an if-block at the start of the code to set properties of the device. Then as new devices are added, I can add new collections of properties. For example:

    if ( device.isApple ) then
    has_back_button = false
    top_padding = 0.1
    end

    if (device.isGoogle) then
    has_back_button = true
    top_padding = 0
    end

    … then, in your code, use these variables. When a new device or variant comes out, you can define a new block for it, with whatever collection of properties applies to that device.

    Otherwise proliferation of devices will make some very ugly code later on, where you’ll end up saying “if is this but not this”, and it’ll be in various places all over your code.

    Also this method makes it much easier to look at your code and understand what differences exist between devices.

    Reply
  7. eugene

    Is there a way to detect cpu speed, if you need to slow down complex effects for slower devices. For example, this would be useful for reducing number of particles in particle effects. If not, is there a way to differentiate iphone 4 from 4s?

    Reply

Leave a Reply

  • (Will Not Be Published)