Conditional coding sample

Java source code

Sample

Let’s go back to our “Hello World !” application. We would like to build the same application, but saying “Goodbye” instead of “Hello”. Instead of creating a new application, we would rather introduce conditional code in the HelloWorld example source code so that both applications can be produced from this same source code.

Note

You will find the ConditionalCoding sample application corresponding to this chapter in the Examples folder of the NeoMAD installation.

First, declare a new String in the CSV text file with the “Goodbye World!” text in all the supported languages.

ID;en;fr;
TXT_HELLO_WORLD;Hello World!;Bonjour le monde!
TXT_GOODBYE_WORLD;Goodbye World!;Au revoir le monde!

Then, we must declare a boolean constant that will express the choice between the application’s two versions. The default value will be true, which means the application will say “Goodbye”.

This is done by adding the following line to the Constants class of our application:

public static boolean SAY_GOODBYE = true;

We can then modify the onCreate() method of the HelloWorldScreen class to initialize the text label using the “Goodbye World!” text instead of “Hello World!” when the SAY_GOODBYE constant is activated:

TextLabel helloWorldLabel = new TextLabel(
                                     Res.string.TXT_HELLO_WORLD);
if (Constants.SAY_GOODBYE) {
   helloWorldLabel.setText(Res.string.TXT_GOODBYE_WORLD);
}

To choose between the two versions of the application, the value of the SAY_GOODBYE constant can be modified either:

  • in the command line
  • using a static block in Constants.java

In this example, the SAY_GOODBYE constant value is true by default, which means this constant is active. To deactivate it the in the command line, use the following option:

-d SAY_GOODBYE=false

This is handy for testing but is not permanent. Now let’s look at the use of the static block in Constants.java. Let’s say we only want our application to say “Hello” to iOS:

public static boolean SAY_GOODBYE = true;
static {
   if (IOS) {
      SAY_GOODBYE = false;
   }
}

The IOS constant comes from TargetInfo and its value is determined at compile time by NeoMAD.

With this static block in the Constants class it is no longer necessary to modify the compilation line. All the binaries can be compiled with:

neomad -t TARGET -m 1,2 -s ConditionalCoding.urs

and all the binaries will say “Goodbye”, except the one for IOS.

NeoMAD optimization step

You may have noted that the SAY_GOODBYE “constant” defined above is not a Java constant (because it is not final). Actually, NeoMAD is currently working on compile time that makes SAY_GOODBYE a true constant. This is why the term “constant” is used above.

Consequently, the Java compiler will create very efficient optimizations when compiling the rest of the code. For the ANDROID target, the optimization of the onCreate() method of the HelloWorldScreen class will produce the equivalent of:

TextLabel helloWorldLabel = new TextLabel(
                                     Res.string.TXT_HELLO_WORLD);
helloWorldLabel.setText(Res.string.TXT_GOODBYE_WORLD);

The point here is to note that the if (Constants.SAY_GOODBYE) test has been removed because the value of SAY_GOODBYE was known at compile time thanks to the optimization step performed by NeoMAD.

Remember!

Conditional coding allows developers to adapt the behavior of their application to each targeted device. This is possible thanks to the TargetInfo interface that provides information about the target devices. TargetInfo contains a set of Java constants that are defined at compile time by NeoMAD.

To take advantage of the optimizations made by NeoMAD, constants used in conditional code must be declared in the Constants class located in the main package. The only types authorized for these constants are the primitive data types and String. Moreover, all the code used to compute the value of these constants must be written in static blocks inside the Constants class.

Although it is possible to declare methods in the Constants class, this is strongly discouraged. NeoMAD will display a warning for each method it encounters in the Constants class.

The “constants” declared in the Constants class don’t need to be declared final. In any case NeoMAD will compute the Constants class to generate true Java constants before compiling the project. This implies that the Java compiler will be able to optimize the code as far as possible.

The value of a constant can be set by using the -d option in the command line. In this case, this definition takes precedence over the computation made in the Constants class.

Note

The value of the constants provided by TargetInfo cannot be changed directly using the -d option in the command line. If you want to redefine the value of a constant provided by TargetInfo, the easiest way is to declare a new constant in the Constants class with the value of the TargetInfo constant and then to redefine the value of this new constant in the command line.

Use of constants in the URS file

Sample

Let’s continue with our example in the “Hello World!” application. Now we also want it to display an image representing the sun or the moon to emphasize the message that is displayed on the screen. We want the image width to be half the screen width. As we are targeting a large range of devices, we cannot use the same image for all targets. Even if some systems, like Android, can re-size the application images at execution time, most will display them with their real size. This means that most of the time, our image will be too small or too large in comparison to the screen size.

We will keep it very simple for the example and use two image sizes corresponding to the screen resolutions 320x480 and 480x800, which correspond to the default screen resolutions for Android. This means that our image width will be 160 and 240 pixels.

In the project, we add the following files in the res directory:

res/
      320x480/
            moon.png
            sun.png
      480x800/
            moon.png
            sun.png

The name of the image files is always the same, but the files are located in a directory named after the resolution of the screen. This kind of organization of the resources is very well suited to conditional resource selection using the URS file.

Now we want to access these images in the application and use the right image depending on the application’s version (defined by the value of the SAY_GOODBYE constant) and the screen width (given by the SCREEN_WIDTH constant of the TargetInfo interface).

First, we have to declare a String constant that will contain the resolution of the screen for the current target. We also write the conditional code used to compute its value:

public class Constants implements TargetInfo {
   public static String RESOLUTION="";
   static {
      if (SCREEN_WIDTH <= 320) {
         RESOLUTION="320x480";
      } else {
         RESOLUTION = "480x800";
      }
   }
}

Note

The code written in the previous steps isn’t shown here in order to not overload the sample.

Then, we have to declare the image resource in the URS file. We declare only two image tags: one for the sun and the other for the moon using the RESOLUTION constant in the file’s path to choose the right image depending on the screen size:

<resourcelot>
   <image name="IMAGE_SUN" path="res/${RESOLUTION}/sun.png"/>
   <image name="IMAGE_MOON" path="res/${RESOLUTION}/moon.png"/>
</resourcelot>

NeoMAD will automatically replace the constant with its value at compilation time.

Note

In order to use constants from Constants.java in the URS file, you MUST use this syntax: ${CONSTANT_NAME} where CONSTANT_NAME refer to the name of the constant in Constant.java.

Finally, we display the image in HelloWorldScreen:

// *** Image Label *** //
// an ImageLabel allows to display a simple image on the screen.
// It can be initialized using a resource id or an Image object.
ImageLabel sunImage = new ImageLabel(Res.image.IMAGE_SUN);
if (Constants.SAY_GOODBYE) {
   sunImage.setImage(Res.image.MAGE_MOON);
}
// we want the ImageLabel size to match the image
sunImage.setStretchMode(MATCH_CONTENT,MATCH_CONTENT);

// add the ImageLabel to the VerticalLayout
layout.addView(sunImage);

Note

This example is aimed at explaining the basics of how to use constants in the URS file. Actually, NeoMAD offers a more advanced mechanism to handle image sizes depending on the screen size of the target for Android and iOS. Please refer to Providing resources for more information.

Remember!

The constants declared in the Constants class can be used in the URS file. The name of constants are replaced by their value at compilation time after the optimization stage.

All the types of constants allowed in the Constants class can be used in the URS file. The values of constants are converted into a string representation, e.g.

  • “47” for an integer
  • “1.2” for a float
  • “true” or “false” for a boolean

The substitution is performed for all the attribute values and all the text in the XML document, except for the following elements: mainclassname, packagename and srcpath.

The substitution is performed only if constants are surrounded by “${“ and “}”.

URS file

Sample

Let’s go back to our very polite “Hello World!” / “Goodbye World!” application. We saw above how to change the application’s behavior depending on the target (see Java source code).

But even if the sun (or moon) image is not used by the application, it is still declared in the URS file and still included in all the binaries created by NeoMAD. This means that the generated file contains useless data. For a lot of good reasons, we do not want this to happen.

Fortunately it’s very easy to “deactivate” resources using conditions in the URS file. To that end, most URS elements have a condition attribute.

In our example we only need to add condition="SAY_GOODBYE" in the moon image declaration:

<image name="IMAGE_SUN"
       path="res/${RESOLUTION}/sun.png"/>
<image name="IMAGE_MOON"
       condition="SAY_GOODBYE"
       path="res/${RESOLUTION}/moon.png"/>

Remember!

Conditions can be specified in the URS file using the condition attribute. This attribute
is available for most elements in the XML document (please refer to the XSD for a complete reference).
The conditions are evaluated using the values of constants from the Constants class. Expressions
are evaluated as Java code so any boolean expression that is valid in Java can be used.

However there is an exception for String constants: the only operators available are ‘==’ and ‘!=’, and the String values must be enclosed by simple quotes.

e.g.

condition="TARGET_NAME == 'IPAD'"

When a resource is “deactivated” using a condition, the corresponding constant in the Res class is still generated but its value is set to -1. So the existence of the resource could be tested in the code using:

if (Res.image.IMAGE_MOON!= -1)

The same resource can be declared several times as long as only one of all the conditions is true for each target, e.g. the following declarations are perfectly valid:

<resourcelot>
   <rawdata condition="GAME_ULTRA"
            name="SOLDIER2R_ANI"
            path="res\sprite\light\soldier2r.ani"/>
   <rawdata condition="!GAME_ULTRA"
            name="SOLDIER2R_ANI"
            path="res\sprite\soldier2r.ani"/>
</resourcelot>

In XML & and < are special characters that must be represented using predefined entities. Consequently, if these characters have to be used in a condition they must be replaced by their entity:

  • &amp; for &
  • &lt; for <

This constraint can make the conditions hard to read. Another solution to use these operators is to create a boolean constant in the Constants class that will evaluate the condition, e.g. to compare two constants named SCREEN_WIDTH and MAX_SCREEN_WIDTH, here is what should be written in the URS file:

condition="SCREEN_WIDTH <= MAX_SCREEN_WIDTH"

Another way to do this is to declare a third constant in the Constants class:

public class Constants implements TargetInfo {
   public static boolean VALID_SCREEN_SIZE = true;
   static {
      VALID_SCREEN_SIZE = (SCREEN_WIDTH <= MAX_SCREEN_WIDTH);
   }
}

And then to use this new constant in the URS:

condition="VALID_SCREEN_SIZE"