First Glance at the Problem
So, it's happened: Your customer really wants your brilliant application. Hooray! But, as it often happens in real life, there is a requirement to support a few different platforms just because the customer has various devices already and is not going to invest more to purchase new ones, at least not at this point. Therefore, you as a developer may face the following problem: How do you develop and maintain such a system? I would say that development itself is not the worst part of it. Maintenance is the beast that may turn into a true nightmare.
From this perspective, Windows Mobile OS is somewhat easier to use. Apart from the SmartPhone and Pocket PC editions, it has "just" a few flavors, such as WM 2003 SE or Win CE 5.0. Not only is the GUI different, but also the MFC libraries differ. Under the Symbian OS, you at least have the Nokia and UIQ GUIs. Each one uses markedly different class libraries for user interface, so you simply can't keep the same sources.
This article gives you a few simple recipes that may help you to survive under such circumstances. There are a few techniques to fulfill multi-GUI requirements. The article will deal mainly with the layout of your project to allow you to manage your source code effectively and with minimum headaches. I will focus more on Symbian OS projects as an excellent example, but most of the approach is applicable for other OSes, such as Windows Mobile.
Review Your Options
When developing mobile applications with any user interface, you probably became familiar with the common programming model used there. Let me restate it here. It will work for both Windows and Symbian OS, so you should begin to explore it. The general approach is to start with four standard classes: Application, Document, Application UI, and View. Such a model (mostly behaving à la the Model-View-Presenter pattern) allows you to separate application logic from the actual representation layer. The latter will vary from platform to platform. So, the application engine remains almost the same, but the GUI may be qiite different.
Your always may keep your code as a solid project and #ifdef it as required. For small projects, it might be enough. But, the bigger the project grows the harder it is to keep it this way. The cons of such an approach are obvious enough. If you forgot to define some #define, you may end up with lost functionality. Also, it leads to awful, unreadable code and curious bugs. The IDE can be confused with syntax coloring and so forth. Sporadic crashes are also possible because you can't be 100% sure you have implemented and configured everything as it was intended. You might want to go even further and provide different functionality for specific platforms; for example, supporting handwriting recognition for tough-screen devices, and not bother with it for others. A monolithic project structure makes it quite difficult.
One of the possible solutions is to use different projects for every platform. In this case, you don't mess with any #ifdef hell, your favorite IDE will be much happier, but such an approach will cost you some effort. Actually, you will have to keep several parallel branches and propagate any new feature developments in the same order via different variants to avoid additional expenses for possible reverse engineering or forward integrations.
You can take one more step further and define even deeper separation, introducing a GUI variant layer in addition to an already existing breakdown into the engine and UI. Each variant should contain only the code relevant for a given platform regardless of application logic. A technique to conduct such a task is as ancient as our world: Define an interface and separate it from an implementation as the first phase and then detect common and variable parts as the second one to place them at appropriate sections of the whole project. Having said this, take a closer look at the project structure.
Project Tree Organization
As you saw above, you are trying to break your solid project into common and variant parts. Therefore, your project tree will consist of common folders for source and header files plus variant parts:
The Include folder contains common headers, Sources keeps common sources, and so forth. Variant-specific code is isolated into corresponding subfolders. Project makefiles, or whatever they will be, reside in a single Workspace directory, one per supported variant. Still, you don't have any additional #ifdefs, so you can compile and develop Variant parts independently.
Now, concentrate on where to put what. The following lists give you some clues on this subject:
- Common Folders
- projectAppUi.h—optional here
- projectAppUi.h—optional here
The source code distribution above is typical enough for a Symbian OS project's structure. As you see, most of the code is divided naturally. Interfaces and base classes belong to the common part, and the variant code goes to its own sections. All project headers for the Application, AppUI, and Document objects live in the Variant area because they should be inherited from different UI frameworks.
The most interesting thing is the AppUI separation. It is divided into two different parts: common and variant-specific. Variant part handles some initialization code, UI commands, menu callbacks and initialisations, and so forth. The amazing fact is that after careful design you may distill a big piece of common code out there!
Placing the projectAppUi.h header to the Common or Variant part of the project reflects some trade-off between implementation effectiveness and maintenance expenses. When you keep it in the Common part, you have to declare some intermediate classes to inherit from and therefore keep additional headers in Variant. On the other side, having AppUi in every Variant allows you much better class definition based on given UI framework features.
Well, now you have designed your application in a smart way, but be careful! Overusage of common code may cause negative effects too. UI frameworks give you the most trivial example here. Because devices are different, GUI framework features are different too, so all this may result in more effort in implementation and support than really needed.
Rhis article has very briefly discussed a design technique you can use to make your application portable between various mobile platforms. It does make your life as a developer easier, yet it requires careful analysis and design of the project. Don't be afraid to refactor your existing code if you have a chance to make it better!
About the Author
Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. After working almost a decade for an international retail software company as a team leader of the Windows Mobile R department, he has decided to dive into Symbian OS ™ Core development.