Posts mit dem Label porting werden angezeigt. Alle Posts anzeigen
Posts mit dem Label porting werden angezeigt. Alle Posts anzeigen

Dienstag, 5. März 2013

Porting Harmattan Apps to Sailfish Silica (and back)

So the Sailfish SDK was released last week, and as explained in the last blog post, gPodder is already running on Sailfish Silica Components. Of course, this has only been possible because Silica is quite similar in API design to Harmattan Qt Components (whenever I write "Harmattan" in this blog post, I usually talk about Harmattan Qt components, and whenever I write "Sailfish" it usually means "Sailfish Silica Components"). But of course porting "from" Harmattan "to" Sailfish with no way back would be kind of annoying - either Harmattan gets dropped, or somebody has to maintain two codebases, something I'd rather avoid. So, just like in "good old" Maemo 4 and Maemo 5 times, the goal here is to convert a Harmattan-only codebase to Harmattan-and-Sailfish, so that both can be maintained in the same codebase and improvements to Harmattan benefit the Sailfish port and vice versa.

In fact, while porting gPodder from Harmattan to Sailfish, it reminded me of porting from Maemo 4 to Maemo 5 - same toolkit (then Gtk+, now Qt/QML), similar extensions (then Hildon, now Components) but different concepts (then normal menus in Maemo 4 vs. HildonAppMenu in Maemo 5, now toolbars and context menus in Harmattan vs. pulley menus in Silica).

Obviously, as gPodder is written in Python here, this applies mostly to PySide-based applications, but the real difference is in the QML (there's really only one short snippet in the Python code that decides which QML import path to use). The approach taken is somewhat simliar to what the Jolla guys have been presenting at FOSDEM 2013, but I don't recall everything and have tailored the approach to better fit my workflow.

Before we start, be sure to know that qt-components is in the Sailfish Emulator (once you install it via "zypper in qt-components" as root), so that could be a shortcut for quick porting exercises and as an interim solution.

For starters, I've cleaned up gPodder's QML UI to make better use of Qt Components (historically, gPodder's QML UI was started at a time where Harmattan Qt Components were not even out or announced, so wazd and me came up with our own style - yes, that's pre-feb11 even!). So, instead of custom transitions and state management, everything is a Page, and transitions happen via the PageStack (the Silica equivalents are also called Page and PageStack). So, assuming you only have pages and so on, you could theoretically already use this or that depending on which platform you are on. Unfortunately, QML doesn't have conditional imports or something like that, so we have to add some abstraction layer.

Here's the basic idea:
  1. Create a new "Components" set - I'm calling mine "org.gpodder.qmlui"
  2. You define which items your components have, and a common API
  3. You implement your components twice: Once for Harmattan, once for Sailfish
  4. At runtime (or even compile time if you would want that) you decide which set of components you use
  5. Your application GUI code only imports QtQuick, maybe QtMultimediaKit (if you need it - it's available on both platforms) and your custom components (org.gpodder.qmlui in my case)
Just in case you are already running to the gPodder Git repo now to check it out, here's a short disclaimer: The UI isn't fully ported yet, so there's still a few remaining "com.nokia.meego" (Harmattan Qt Components) imports in my QML UI, but that's just temporary until I have found a good replacement for things like Sheet and MultiSelectionDialog in Silica.

My custom components at this time look like this:
Most of these are just thin wrappers around the "native" components on the target platform. For example, WindowWindow.qml (harmattan, sailfish) is just a wrapper for PageStackWindow (on Harmattan) and ApplicationWindow (on Sailfish). Where I said "import com.nokia.meego 1.0" and "PageStackWindow {}" previously, I now say "import org.gpodder.qmlui 1.0" and "WindowWindow {}". In the case of the Harmattan implementation, this is just the same as before, but on Sailfish, an ApplicationWindow will be used.

Other wrappers just deal with API differences. For example, ScrollDecorator exists in both Harmattan and Sailfish, but in Harmattan the property is called "flickableItem" and on Sailfish it's "flickable". So my ScrollScroll (harmattan, sailfish - also, noticed a naming pattern there? ;) takes care of hiding the differences, and I just use "flickable" on a ScrollScroll, and it will do The Right Thing on the platform it's currently running on.

When the API is the same (e.g. for Button), you still need to create pass-through components because of the different import path: Button (harmattan, sailfish).

But there's also some more elaborate differences. For example, I want to have menus - a toolbar menu on Harmattan and a pulley menu on Sailfish. So I defined a very simple Action (I'd be surprised if such a component doesn't already exist, but I didn't find one when I wasn't looking (sic)) that basically has a text and some signal attached to it. In my application code, I define actions as a property on my PagePage (which is a Page with some special code to transform actions into whatever the platform representation is). See the main page actions for an example. The ActionMenu (harmattan, sailfish) then takes care of creating a ContextMenu (Harmattan) or PullDownMenu (Sailfish) and PagePage (harmattan, sailfish) takes care of creating a toolbar on Harmattan and forwarding a reference to the listview to which the PullDownMenu should be attached to (in Harmattan, I still need to pass the listview there, but it's not used).

The same customization also happens for the ListList (harmattan, sailfish), which is a ListView (Harmattan) or SilicaListView (Sailfish), but also takes care of displaying a header (which still needs to be styled correctly on Sailfish) if the platform requires it.

Once you've created your custom set of components, you have to create a qmldir file that lists the components available and in which version of the components they are available. Read up on Declarative Modules in the official documentation.

So there you have it. One QML codebase, two UX targets that are well-integrated. You can see the results in the just-released gPodder 3.5.0, which is more Harmattan-ish on the N9 than before (although some items have moved from the toolbar into the menu for simplicity reasons, and I actually think it's cleaner now) and also looks and feels quite native on Sailfish (it's not done yet, and I'm sure the UI will evolve and adapt once Silica gets more mature and we see more Sailfish apps).

What I'd like to see in the Sailfish SDK: Different Ambiance backgrounds (darker, brighter, different hues) so that developers like me can test if their apps look good atop more than just the nice blue default background.

As a last hint (I couldn't find that in the API docs, but in the component gallery): Sailfish Silica's Label Component has a "truncationMode" property. If you set it to "TruncationMode.Fade", you never want to see elided text again, because it looks so sexy! :)

Sonntag, 24. Juni 2012

qw The Game ported to Android

I decided it's time to port qw The Game to Android, as a way to see how the publisher story works on Google Play compared to Ovi Publish and also to see how mature the Qt port to Android is.

For my port, I used Necessitas alpha 3 update 4, which you can download from the project website. Necessitas already includes both the Android SDK (that's the SDK you use in general, and contains all the Java APIs, etc..) and the Android NDK (that's used if you want to do native C/C++ development), so no need to install anything else. You can start Necessitas Qt Creator using "QtCreator/bin/qtcreator" in the Necessitas installation directory, but Necessitas also creates a launcher icon if you shy away from command lines.

The assumption is now that you have an existing Qt project with a qmake project file (.pro). Just open that .pro file in Necessitas Qt Creator and let it create the Android scaffolding. You can now try to build the project and run it - either in the Android emulator, an actual Android device or your N900, N950 or N9 running Nitdroid (the N950/N9 port is actually quite nice these days, and can be installed alongside Harmattan by flashing an open mode kernel with Android patches, replacing some init files and placing the Nitdroid rootfs inside /home/nitdroid/).

When you first start a Qt-based application on Android, it will redirect you to Google Play to download Ministro, which will take care of downloading the Qt libraries and storing them in a central, shared place, so that all Qt-based apps can use the same Qt libraries (which means that you can do closed-source apps, because they are dynamically linked to the LGPL'd Qt, and it also means that apps are smaller, because Qt doesn't need to be shipped with each app). One disadvantage is that the end user experience is not seamless on first startup, because your users will have to install Ministro first and wait for the Qt libraries to download.

Anyway, now you got that far and got your application running - or not. If you are missing libraries, and your application doesn't start, open the "Projects" tab in Necessitas Qt Creator and click on the "Run" tab for the Android target. There, open "Package configurations" and click on "Libraries" and check all Qt libraries that you need. When you compiled your application, there's a button ("Read information from application") that will analyze your binary and check the right Qt libraries. You can also add "Prebundled libraries", which are dynamically-linked shared libraries that will be shipped with your application. You can also check out the "Manifest", "Application" and "Permissions" tabs and configure it to your liking.

Protip: You can build the Android package on the command line using "ant" as soon as you have used Necessitas Qt Creator to create the initial scaffolding. The "android" folder will be created inside the folder where your .pro is located, and there you will find a build.xml.

Now to some code-level customizations that I had to do for my game, qw:
  • QtMultimediaKit was not found - I had to comment out sound effects for now
  • Automatic screen rotation - My game is landscape-only, and I had to tell the Android system in the AndroidManifest.xml file
  • Qt.labs.particles was not found - Again, I simply commented it out for now; I guess these QML plugins should be easy to add to Necessitas, but I didn't bother to find out for this first try
  • Screen resolutions! - Don't assume 800x480 (in case of the N900) or 854x480 (in case of the N950 and N9); make your app scale properly to the screen resolution at hand. Test this by creating a non-fullscreen window on your Desktop in different sizes, and see if the content looks right. For a quick'n'dirty first try, use QGraphicsView::scale
As far as the publishing experience goes: You have to pay a one-time fee of $25 to be allowed to publish to the Play Store (compare this to a one-time fee of 1 Euro for Ovi Publish and a yearly fee of $99 for the iOS App Store). What I found interesting is that there's basically no QA (apart from automated checks that are very helpful and give guiding error messages) and an application uploaded is published in a matter of minutes. I also like the fact that you can add a YouTube video link to the Play Store submission, so users can get a preview of how the experience is like. I published the app for free, and the UI warned me that it's not possible to convert a free app into a paid one in the future, which doesn't matter for me, but it might be a useful hint for other publishers.

qw The game is now available on Google Play for free. It's also available in Nokia store for free. Go play!