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:
- & for &
- < 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"