Thursday, March 19, 2015

JavaFX on mobile, a dream come true!

Hi there!

It seems is time for a new post, but not a long and boring one as usual. I'll post briefly about my first experience bringing JavaFX to Google Play store.

Yes, I made it with 2048FX, a JavaFX version of the original game 2048, by Gabriel Cirulli, that Bruno Borges and I started last year.

Now, thanks to the outstanding work of JavaFXPorts project, I've adapted that version so it could be ported to Android. 

And with the very last version of their plugin, I've managed to succesfully submit it to Google Play store.

After a week in beta testing mode, today the app is in production, so you can go and download it to your Android device, and test it for yourself.

For those of you eager to get the app, this is the link. Go and get it, and add a nice review ;)

If you want to read about the process to make it possible, please keep on reading. These are the topics I'll cover in this post:
  • 2048FX, the game
  • JavaFXPorts and their mobile plugin
  • New Gluon plugin for NetBeans
  • 2048FX on Android
  • Google Play Store

2048FX, the game


Many of you will now for sure about the 2048 game by Gabriel Cirulli. Last year it was a hit.
Many of us got really addicted to it...


In case you are not one of those, the game is about moving numbers in a 4x4 grid,  and when equal numbers clash while moving the blocks (up/down/left/right), they merge and numbers are added up. The goal is reaching the 2048 tile (though you can keep going on looking for bigger ones!).

At that time, Bruno started a Java/JavaFX 8 version, given that the game was open sourced. I jumped in inmediately, and in a few weeks we had a nice JavaFX working version


Since we used (and learned) the great new features of Java 8, we thought it was a good proposal for JavaOne, and we end up presenting it in a talk (video) and doing a Hands on Lab session.

And we'll talk about it again next week at JavaLand. If you happen to be there, don't miss our talk

JavaFXPorts and their mobile plugin


Since the very beginning of JavaFX (2+), going mobile has been on the top of list of the most wanted features. We've dreamed with the possibility of making true the WORA slogan, and it's only recently since the appearance of the JavaFXPorts project, that this dream has come true.

Led by Johan Vos, he and his team have given to the community the missing piece, so now we can jump to mobile devices with the (almost) same projects we develop for desktop.

While Johan started this adventure at the end of 2013, his work on porting JavaFX to Android, based on the OpenJFX project, has been evolving constantly during 2014, until recently in Febrary 2015 he announced a join effort between his company, LodgOn, and Trillian Mobile, the company behind RoboVM, the open source project to port JavaFX to iOS.

As a result, jfxmobile-plugin, the one and only gradle JavaFX plugin for mobile was created and freely available through the JavaFXPorts repository.

With this plugin you can target from one single project three different platforms: Desktop, Android and iOS.

An it's as simple as this sample of build.gradle:
 
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.javafxports:jfxmobile-plugin:1.0.0-b5'
    }
}

apply plugin: 'org.javafxports.jfxmobile'

repositories {
    jcenter()
}

mainClassName = 'org.javafxports.project.MainApplicationFX'

jfxmobile {
    ios {
        forceLinkClasses = [ 'org.javafxports.**.*' ]
    }
}

For Android devices, it is required Android SDK, and Android build-tools as you can read here. The rest of the depencencies (like Dalvik SDK and the Retrolambda plugin) are taking care by the plugin itself.

Note the plugin version 1.0.0-b5 is constantly evolving, and at the time of this writting 1.0.0-b7 is now available. Check this frequently to keep it updated.

With this plugin, several tasks are added to your project, and you can run any of them. Among others, these are the main ones:
  • ./gradlew android creates an Android package
  • ./gradlew androidInstall installs your Android application on an Android device that is connected to your development system via a USB cable.
  • ./gradlew launchIOSDevice launches your application on an iOS device that is connected to your development system
  • ./gradlew run launches your application on your development system.

New Gluon plugin for NetBeans


Setting up a complex project, with three different platforms can be a hard task. Until now,  the best approach (at least the one I followed) was cloning the HelloPlatform sample, and changing the project and package names.

But recently, a new company called Gluon, with Johan as one of his founding fathers,  has released a NetBeans plugin that extremelly simplifies this task.

Once you have installed the plugin, just create a JavaFX new project, and select Basic Gluon Application.



Select valid names for project, packages and main class, and you will find a bunch of folders in your new project:


Change the jfxmobile-plugin version to 1.0.0-b5 (or the most recent one), select one of the tasks mentioned before, and see for yourself.

 2048FX on Android


I've been using previous versions of the plugin, and it was a hard task to get everything working nicely. In fact, I had a working version of 2048FX on Android before the announcement of the jfxmobile-plugin. But it was a separated project from the desktop one.

Now with the plugin, everything binds together magically. So I have a single project with the desktop and the android version of the game.

Java 8?


There's a main drawback in all this process: Dalvik VM doesn't support Java 8 new features. For Lambdas, we can use the Retrolambda plugin, that takes care of converting them to Java 6 compatible bytecode. But Streams or Optional are not supported. This means that you have to manually backport them to Java 6/7 compatible version.

While the primary object of the 2048FX project was basically learning these features, for the sake of going mobile, I backported the project, though this didn't change its structure or its appearance.

The project: Desktop and Android altogether


This is how the project structure looks like:



A PlatformProvider interface allows us to find out in which platform we are running the project, which is extremely useful to isolate pieces of code that are natively related to that plaftorm.

For instance, to save the game session in a local file in the Android device, I need to access to an internal folder where the apk is installed, and for that I use an FXActivity instance, the bridge between JavaFX and Dalvik runtime, that extends Android Context.  This Context can be used to lookup Android services. 

One example of this is FileManager class, under Android packages:

import javafxports.android.FXActivity;
import android.content.Context;
import java.io.File;

public class FileManager {
    private final Context context;
    
    public FileManager(){
        context = FXActivity.getInstance();
    }
    
    public File getFile(String fileName){
        return new File(context.getFilesDir(), fileName);
    }
    
}

Now the PlatformProvider will call this implementation when running on Android, or the usual one for desktop.

After a few minor issues I had a working project in both desktop and Android.




Google Play Store


Bruno asked me once to go with this app to Google Play store, but at that time the project wasn't mature enough. But last weekend I decided to give it a try, so I enrolled myself in Google Play Developers, filled a form with the description and several screenshots, and finally submitted the apk of the game... what could go wrong, right?

Well, for starters, I had a first error: the apk had debug options enabled, and that was not allowed.

The AndroidManifest.xml


 This hidden file, created automatically by the plugin, contains important information of the apk. You can retrieve it after a first built, and modify it to include or modify different options. Then you have to refer this file in the build.gradle file.

On the application tag is where you have to add android:debuggable="false".

There you can add also the icon of your app: android:icon="@mipmap/ic_launcher", where mipmap-* are image folders with several resolutions.

Signing the apk


Well, that part was easy. Second attempt, second error... The apk must be signed for release. "Signed" means you need a private key, and for that we can use keytool.

And "release" means that we need to add to build.gradle the signing configuration... and that was not possible with the current plugin version b5.

So I asked Johan (on Friday night) about this, and he answered me (Saturday afternoon) that they've been working precisely on that but it was not ready yet. Later that evening, Joeri Sykora from LodgON, told me that it was in a branch... so with the invaluable help of John Sirach (the PiDome guy) we spent most of the Saturday night trying to build locally the plugin to add the signing configuration. 

It end up being something like this:

jfxmobile {
    android {
        signingConfig {
            storeFile file("path/to/my-release-key.keystore")
            storePassword 'STORE_PASSWORD'
            keyAlias 'KEY_ALIAS'
            keyPassword 'KEY_PASSWORD'
        }
        manifest = 'lib/android/AndroidManifest.xml'
        resDirectory = 'src/android/resources'
    }
}

It was done! It was almost 2 a.m., but I tried uploading the signed apk for the third time, and voilà!! No more errors. The app when to submission and in less than 10 hours, on Sunday morning it was already published!!

  

Beta Testing Program


Instead of going into production I chose the Beta Testing program, so during this week only a few guys have been able to access to Google Play to download and test the application.

Thanks to their feedback I've made a few upgrades, like fixing some issues with fonts and Samsung devices (thanks John) or changing the context menu to a visible toolbar (thanks Bruno).



2048FX on Google Play Store


And the beta testing time is over. As of now, the application is on production. 

What are you waiting for? Go and get it!! 

Download it, play with it, enjoy, and if you have any issue, any problem at all, please report it, so we can work on improving its usability in all kind of devices.

Final Thanks


Let me finish this post taking the word of the whole JavaFX community out there, saying out loud:


THANK YOU, JavaFXPorts !!!

Without you all of this wouldn't be possible at all.

Big thanks to all the guys already mentioned in this post, and also to Eugene Ryzhikov, Mark Heckler and Diego Cirujano, for helping along the way.

And finally, thanks to the OpenJFX project and the JavaFX team.

UPDATE

Thanks to the work of Jens Deters, 2048FX has made it to Apple Store too!



Go and install it from here!

And since today (15th May 2015), we are open sourcing all the project, so anyone can have a look at it and find out about the last final details required to put it all together and make it successfully to Google Play or Apple Store:

https://github.com/jperedadnr/Game2048FX

Enjoy!
 

59 comments:

  1. Thank you José for this great example!

    ReplyDelete
    Replies
    1. This comment has been removed by a blog administrator.

      Delete
  2. Hi José! And: Great work!
    I have some questions though:
    1) Is the source code available somewhere? It would be a great example of how to create a more complex application then the example Hello World from Johan :-)
    2) Do you need some help with translations? I could provide you a German one, if you want to...

    PS: Since I'm developing for Android on a regular base: If you have some questions, just ask...

    ReplyDelete
  3. Hi jose, good work. I'm developing a project using Javafxports and your article help me so much
    Thanks.

    ReplyDelete
  4. Hi jose, good work. I'm developing a project using Javafxports and your article help me so much
    Thanks.

    ReplyDelete
  5. 8192 it´s mi record in this game, now go to 16384

    ReplyDelete
  6. Thanks for the great article! From the structure screenshot I can see you put the Android specific classes into the Android folder created by the Gluon plugin. Now I did the same, however classes in the folder "source packages [java]" cannot import the Android classes in my project (symbol not found). How did you access e.g. the FileManager from within your JavaFX code?

    ReplyDelete
    Replies
    1. Note that in my source package there are two classes: PlatformService and PlatformProvider. You can find them on the HelloPlatform project: https://bitbucket.org/javafxports/samples, among the rest of the required files for the three platforms. On the interface you can add some method signatures like:

      File getFile(String fileName);

      and then you have to implement it on every platform. For instance, in AndroidPlatformProvider using FileManager:

      @Override
      public File getFile(String fileName) {
      FileManager fileManager=new FileManager();
      return fileManager.getFile(fileName);
      }

      Finally, on your source files, you can request at any time a valid path for your files:

      File file = PlatformService.getInstance().getFile();

      Alternatively, the new plugin from Gluon, Gluon-charm-down https://bitbucket.org/gluon-oss/charm-down takes care of this for you, and you just need to add:

      File path = PlatformFactory.getPlatform().getPrivateStorage()
      File file = new File(path,);

      Delete
  7. How did you managed your graphical work ? I have seen graphics file no where , except some icons.

    ReplyDelete
  8. Everything but the icons for the app is done with CSS. You can find the file game.css on the link to the repo I provided.

    ReplyDelete
  9. This comment has been removed by a blog administrator.

    ReplyDelete
  10. is it possible to run the basic gluon application created with netbeans 8.1 which runs fine on desktop but on android jellybean 4.1.xxx build ? what should be the build.gradle configuration . Does the minimum sdk 5.0.1 API should be 21.xxx. Please advice. Iam new to android.

    ReplyDelete
    Replies
    1. Yes, you should be able to run the basic Gluon application on your android device. Check this link http://docs.gluonhq.com/javafxports/#anchor-3 for more details on default configuration.

      Delete
  11. Jose, JC here. Awesome work ;)

    Please drop me a line if you have a minute, to refresh contact.
    Your email at EII no longer works...

    Cheers
    JC
    __________

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete
  13. This comment has been removed by a blog administrator.

    ReplyDelete
  14. Hey JP,
    first off all - thanks for your great article! I managed to build an app that now runs on android using FXML and the gluon plugin. Filled in the missing informations from their documentation with the infos provided in your article.

    The only thing that i'm not grasping is: How can i run the application on desktop? There is a build task called dist --> distZip. But neither the .bat nor the Unix starter starts the application on windows nor on OS X. On the other hand - if i click "run", the app builds and runs nicely. Any Ideas what i might be missing?

    ReplyDelete
    Replies
    1. Thanks for your comment

      As for running on desktop, "./gradlew run" will run the app, but if you want to distribute it as an application, "distZip/distTar" or "installApp" will just create a lib folder with all the jars and a script to launch it.

      Note that if there are "desktop" dependencies (if you are use "desktopCompile" or "desktopRuntime"), those dependencies will be missing from script and folder (this is a bug). To solve it, you can change those to "compile", so you make sure you have all the dependencies in that lib folder.

      As for packaging the application to create an installer, you may have a look at this plugin for your build.gradle: https://bintray.com/shemnon/javafx-gradle/gradle-javafx-plugin/8.1.1/view

      Jose

      Delete
  15. So ,you were using Android API's too in this project?right!!

    ReplyDelete
    Replies
    1. For the Android required services, yes, you need the Android API. Now most of the code in the Android/iOS packages could be removed as those services can be found in Gluon Charm Down. But anyway, Charm Down uses Android API as well.

      Delete
  16. This comment has been removed by the author.

    ReplyDelete
  17. This comment has been removed by a blog administrator.

    ReplyDelete
  18. Thanks a lot for this article !
    Thanks to you i could make my javaFX game works on my phone :)
    but i tried to publish it on the google play store, i added the signing key using keytool but google tells me my app is only debug signed and doesn't accept it..
    Do you know what to do, i followed all you said i think..

    Thanks a lot !

    ReplyDelete
    Replies
    1. Thanks!
      If you check the AndroidManifest file: https://github.com/jperedadnr/Game2048FX/blob/master/lib/android/AndroidManifest.xml
      you need to set the debuggable option for the Application tag as false: android:debuggable="false"

      Delete
    2. Oh thanks a lot for answering, I was justly coming to edit my question, i found a solution, something very stupid in fact, i was building using the command android and not androidrelease ^^'
      I'm currently waiting google to publish :)

      Again thank you so much, for this article and all your answers you made for other people on stackoverflow.

      Kind regards

      Delete
  19. This comment has been removed by a blog administrator.

    ReplyDelete
  20. This comment has been removed by a blog administrator.

    ReplyDelete
  21. This comment has been removed by a blog administrator.

    ReplyDelete
  22. This comment has been removed by a blog administrator.

    ReplyDelete
  23. This comment has been removed by a blog administrator.

    ReplyDelete
  24. This comment has been removed by a blog administrator.

    ReplyDelete
  25. This comment has been removed by a blog administrator.

    ReplyDelete
  26. This comment has been removed by a blog administrator.

    ReplyDelete
  27. This comment has been removed by a blog administrator.

    ReplyDelete
  28. This comment has been removed by a blog administrator.

    ReplyDelete
  29. This comment has been removed by a blog administrator.

    ReplyDelete
  30. This comment has been removed by a blog administrator.

    ReplyDelete
  31. This comment has been removed by a blog administrator.

    ReplyDelete
  32. This comment has been removed by a blog administrator.

    ReplyDelete
  33. This comment has been removed by a blog administrator.

    ReplyDelete
  34. This comment has been removed by a blog administrator.

    ReplyDelete
  35. This comment has been removed by a blog administrator.

    ReplyDelete
  36. This comment has been removed by a blog administrator.

    ReplyDelete
  37. This comment has been removed by a blog administrator.

    ReplyDelete
  38. This comment has been removed by a blog administrator.

    ReplyDelete
  39. This comment has been removed by a blog administrator.

    ReplyDelete
  40. This comment has been removed by a blog administrator.

    ReplyDelete
  41. This comment has been removed by a blog administrator.

    ReplyDelete
  42. This comment has been removed by a blog administrator.

    ReplyDelete
  43. This comment has been removed by a blog administrator.

    ReplyDelete
  44. This comment has been removed by a blog administrator.

    ReplyDelete
  45. This comment has been removed by a blog administrator.

    ReplyDelete
  46. This comment has been removed by a blog administrator.

    ReplyDelete
  47. This comment has been removed by a blog administrator.

    ReplyDelete
  48. This comment has been removed by a blog administrator.

    ReplyDelete
  49. This comment has been removed by a blog administrator.

    ReplyDelete
  50. This comment has been removed by a blog administrator.

    ReplyDelete
  51. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.