At a first glance, reading data files in Flex is easy. But sometimes things which seem simple (and should be simple!) actually prove to be tricky... I would like to share my experience in this area, hoping it may be of some help to others.
The problem to solve
Let's start with a description of a set of development requirements:
- Write Flex web applications (not using AIR) which load a localized data file. For example, an XML file containing strings translated into various languages.
- Additional requirement: cope with cases when translations are not available for all supported locales.
- Also, allow adding a translation without modifying the source code of the app.
- And (of course!), maximize performance and minimize bandwidth consumption for running over the web.
The above requirements look quite analogous to those of the Flex mechanism for dealing with resource bundles for the localization of Strings in ActionScript or MXML, for which Flex provides already everything you need (see Using resources
in Flex documentation). Furthermore, we do want a similar policy for loading localized data files as the one Flex uses for loading resource bundles for a given locale chain: The Flex application uses the list of locales in the localeChain
property (of ResourceManager) to determine precedence when getting values from resource
bundles. If a value does not exist in the first locale in the list, the
Flex application looks for that value in the next locale in the list,
and so on.
(citation from Flex documentation
But this resource bundle mechanism does not apply (at least not directly) to the loading of data files. So let's try to implement it by our own. At a first glance, it should be a piece of cake:
- Package the data files in locale-dependent subdirectories of the application (as for property files), say "data/fr_FR/mydata.xml", "data/en_US/mydata.xml". Each directory contains the localized variant for the corresponding locale.
- At runtime, gather the localeChain (app.resourceManager.localeChain) and iterate over the locales to obey the precedence rule.
- For each locale, check whether the data file is present.
- If present, read it and use it, and we're done. If not present, go to the next locale, till the end of the locale chain. If not found for any locale in the locale chain, fall back to some predefined default locale (typically but not necessarily English).
Algorithmically quite trivial... but step #3 proved to be much trickier to implement than I initially thought. That is, checking whether a data file is present in a given directory isn't that simple: you discover it once you try it... Let's see the available Flex APIs for loading data files in webapps. The package "flash.net" provides URLStream, which, according to its doc, "provides low-level access to downloading URLs. Data is made available to application code immediately as it is downloaded, instead of waiting until the entire file is complete as with URLLoader. The URLStream class also lets you close a stream before it finishes downloading. The contents of the downloaded file are made available as raw binary data from an URL".
Fine. But for our purpose we need to iterate over URLs with relative parts such "data/fr_FR/mydata.xml" and, "data/en_US/mydata.xml" and, for each, to determine as quickly as possible if the given file is available. No API is dedicated for this precise task: checking for file availability. Instead, we have to actually start a loading of the file using URLStream.load(URLRequest). Then we try to figure out whether the file exists. But how do we figure it out? The Flex API offers several types of notifications:
- URLStream.load throws IOError or SecurityException exceptions "if the loading fails immediately" (more exactly, both exception classes are mentioned in the asdoc, however only the SecurityException is listed in the throws clauses of the asdoc).
- Events are sent to the registered listeners of IOErrorEvent.IO_ERROR and SecurityErrorEvent.SECURITY_ERROR events. Obviously, this holds for errors that occur asynchronously.
- Events are also sent to registered listeners of HTTPStatusEvent.HTTP_STATUS events. This event contains an event.status field.
- Finally, events are also sent to registered listeners of Event.OPEN, ProgressEvent.PROGRESS, and Event.COMPLETE events.
So, these APIs offer several means to detect either a loading failure, or a loading success. Can we just rely on just one of them in all cases? Testing in various cases brought a negative answer... The behavior of these APIs varies from one testing platform to another, for reasons such as design or implementation choices or bugs in Flex/Flash, variety of behaviors of browsers and webservers. All in one, I had to use a combination of the means mentioned above to be able to put in place an utility function which works in all the use cases that we tested.
Examples of behaviors which are browser-dependent:
- The status field of HTTPStatusEvent.HTTP_STATUS events: depending on browsers, FlashPlayer may not be able to provide the
correct status and provides instead the value 0. See Adobe bug FP-721
"URLLoader - HttpStatusEvent should return a valid http status code and
ProgressEvent.PROGRESS : for instance with Internet Explorer 8 (and FlashPlayer10.3), we get
a "progress" event even for an URL corresponding with a non-existing
file! Furthermore, this "progress" event has event.bytesAvailable >
0! Indeed, the stream can contain in case of error an HTML page with an
error message... (and its precise content depends on the webserver)
We called this utility function AsyncFileLoader.asyncLoadLocalized.
The sample application uses the utility function AsyncFileLoader.asyncLoadLocalized and provides a GUI which allows to try it in various cases. The GUI is mostly self-explanatory. It has three parts:
- Top part: There are two buttons: one for loading a data file which is available for fr_FR and en_US locales (French and English translations), but not for ru_RU locale. The second button is for launching the loading of a data file which is available for none of the locales. The combo box allows to switch among all possible combinations of order among the three locales (ru_RU, fr_FR, and en_US).
- Middle part: shows the content of the loaded file (when the loaded succeeds).
- Bottom part: shows the log (the output of debugging statements in the utility function).
Here is how the sample application looks like:
Screenshot of the sample application (click to enlarge).
In this example, the log shows the execution traces corresponding to the loading in Internet Explorer 9 of a data file which is available for the second locale in the locale chain, but not for the first locale. The important point is that, as you can see, the execution sequence includes, for Internet Explorer (at least 8 and 9), an event of type "open" even for a file which does not exist! This does not hold for Chrome or Firefox, but it means we can't blindly consider that a file exists just because we received an "open" event! As a matter of fact, the loading goes differently depending on web browsers, and potentially Flex and Flash versions (see the Adobe bug mentioned above), and web servers (which may react differently to requests for unavailable resources). The utility aims to cope with a variety of behaviors while detecting as soon as possible whether a file exists or not for a given locale. There is no theoretic guarantee that it works optimally in all possible cases, but it has been successfully tested on all the combinations of OS and
browser supported by Flash Player 10.3 (recent versions of Chrome,
Firefox, Internet Explorer, Opera, Safari, on Windows, Linux, and Mac
You can run the sample application by clicking here
. To view the source code, right-click anywhere in the application and press "View Source".