Designing a
JavaFX Mobile
application

Fabrizio Giudici
Tidalwave s.a.s.
214
AGENDA

> Putting JavaFX (Mobile) in context
> My case study
> Main features of JavaFX (Mobile)
> JavaFX Mobile through a few examples
> Current status of JavaFX Mobile
> Conclusion




                                         2
About the speaker

> Fabrizio.Giudici@tidalwave.it
> Senior Architect, Mentor, Technical Writer
> Working with Java (all the editions, plus exotic stuff) since 1996
> Working with Java ME since 1999
   –   First J2ME edition at JavaOne™ (Palm)
   –   Developed a couple of research projects with the University of Genoa
   –   Developed a couple of customer projects with my former company
   –   Consulting for customer projects
   –   Author of mobile FLOSS: windRose and blueBill Mobile
> Co-leader of JUG Milano


                                                                              3
What's wrong with JME

> Everything was so good at the old, Palm times
  – Small devices → no big expectations
   –   One reference platform
   –   One pervasive runtime
> With more devices, JME got fragmented
  – JSR as extension points (e.g. bluetooth, location)
   –   Multiple combinations
   –   Harder and harder to test
   –   windRose pain



                                                         4
Hoping in Java FX Mobile

> JavaFX announced by Sun Microsystems in 2007
  – A specific scripting language + a runtime
   –   Fight the “Ugly Java UI stereotype”
   –   Re-designed UI controls
   –   Integration with graphics designers workflow (e.g. NetBeans + Photoshop)
   –   Profiles for multiple targets (desktop / web, mobile)
> JavaFX Mobile profile
  – Can sit on top of JME (MSA: Mobile Service Architecture, a.k.a. JSR-248)
   –   Maybe no more (or reduced) fragmentation?
> Oracle commitment after the Sun buy
> A fulfilled promise?
   –   Answers at the end :-)                                                     5
My case study: blueBill Mobile

> “The field tool for birdwatchers”
  – Records geotagged observations
   –   Reference guides with multimedia
   –   Social capabilities (à la Google Friends)
> Started in 2009 with JavaFX
> Currently the most advanced version is the Android one
   –   JavaFX version being redesigned (more later)
> Demo




                                                           6
Main features of JavaFX

> The languages is both imperative and declarative
  – Imperative: for normal processing
   –   Declarative: mostly for the UI (no XML, no separate layout descriptor)
              Binding
   –   Has got closures, mixins
> Compiles to the same VM bytecode
  – Can mix with Java code (e.g. reference a Java library)
   –   On the desktop, can be mixed with Swing (in addition to its own UI widgets)
   –   On the mobile, runs its own UI widgets




                                                                                     7
Some code examples

> Example 1: Calling JME code
> Example 2: Posting contents (REST)
> Example 3: Getting resources (REST)
> Example 4: Binding (if time allows)




                                        8
Example: calling JME code

> Not much to say: it just works
package it.tidalwave.bluebillmfx;

import   javax.microedition.location.Criteria;
import   javax.microedition.location.LocationProvider;
import   javafx.animation.Timeline;
import   javafx.animation.KeyFrame;
import   it.tidalwave.geo.mapfx.model.Coordinates;

public class PositionTracker
  {
    public-read var currentPosition : Coordinates;
    def criteria = new Criteria();
    var locationProvider : LocationProvider;

    postinit {
        locationProvider = LocationProvider.getInstance(criteria);
        pollingTimeline.play();
      }


                                                                     9
Example: calling JME code

> Need to copy data in JavaFX classes if you want binding support
     function getPosition(): Void {
         def location = locationProvider.getLocation(5); // JME stuff
         def coordinates = location.getQualifiedCoordinates();

           if (coordinates != null ) {
               def lat = coordinates.getLatitude();
               def lon = coordinates.getLongitude();
               currentPosition = Coordinates { latitude: lat; longitude: lon };
             }
       }

     def pollingTimeline = Timeline {
          repeatCount: Timeline.INDEFINITE
          keyFrames:   KeyFrame { time: 10s; action: periodicTask }
       };

     function periodicTask(): Void {
         getPosition();
         // update the map position, etc...
       }
 }                                                                                10
Example: posting contents

> HttpRequest provides a skeleton
public class ObservationRDFUpload extends HttpRequest
  {
    public-init var serviceURL = "http://myHost:8084/blueBillGeoService";

      override public var method = HttpRequest.POST;

      public var content : String;

      override var output on replace {
          if (output != null) {
              output.write(content.getBytes());
              output.close();
            }
        }

      override function start(): Void {
          location = "{serviceURL}/postObservation"; // REST call
          setHeader("Content-Type", "application/rdf");
          super.start();
        }
  }                                                                         11
Example: posting contents

> HttpRequest provides a skeleton

 def upload = ObservationRDFUpload
   {
      content: // ... assemble the string with the contents
   };

 upload.start();




                                                              12
Example: reading resources

> The relevant model class
public class Taxon
  {
    public-read protected var id : String;
    public-read var urlsLoaded = false;
    var urlsLoading = false;
    var imageURLs : String[]; // [] is a list (“sequence”), not an array
    var images : DeferredImage[];

    public bound function getImage (index : Integer): DeferredImage {
        def dummy = loadImageURLs(); // assignment is a requirement for the bound function
        return images[index];
      }

    public bound function imageCount(): Integer {
        return sizeof images;
      }




                                                                                             13
Example: reading resources

> Getting a JSON resource catalog
  function loadImageURLs(): Boolean {
      if (not urlsLoading and not urlsLoaded) {
          urlsLoading = true;
          def fixedId = clean(id); // get rid of/escape “bad” chars such as :, #, etc...
          def url = "http://kenai.com/svn/bluebill-mobile~media/catalog/{fixedId}.json";

         def request : HttpRequest = HttpRequest { // mere instantiation, not subclassing!
             location: url;
             method:   HttpRequest.GET;

              onInput: function (is: InputStream) { // hey, this is not a switch() label!
                  if (request.responseCode != 200) {
                      imageURLs = "media/black_glossy_web20_icon_012.png";
                    }
                  else {
                      try {
                          def parser = PullParser { documentType: PullParser.JSON;
                                                    input: is; onEvent: parseEventCallback };
                          parser.parse();
                        }
                      catch (e: Exception) {
                          println("Exception {e} while parsing {url}");
                        }
                      finally {
                          is.close();                                                           14
                        }
                    }
                }
Example: reading resources

> Getting a JSON resource catalog
                     onException: function (e: Exception) {
                         imageURLs = "media/black_glossy_web20_icon_012.png";
                         urlsLoaded = true;
                       }

                     onDone: function() {
                         images = for (url in imageURLs) {
                             DeferredImage { url: url };
                           }

                          imageURLs = null;
                          urlsLoaded = true;
                      }
                 }

               request.start();
           }

         return urlsLoaded;
     }

  def parseEventCallback = function (event: Event): Void {
       if ((event.type == PullParser.TEXT) and (event.name == "url")) {
           insert event.text into imageURLs;
         }                                                                      15
     }
 }
Example: reading resources

> Loading an image
> JavaFX Image loads automatically when initialized
var imageCounter = 0;
public class DeferredImage
  {
    public-init var url : String;
    public-read var progress = bind image.progress;
    public-read var error = bind image.error;
    def id = imageCounter++;
    public-read var image : Image;

      public function load() { // start loading only when load() is called
          if (image == null)
            {
              image = Image {
                  url: url
                  backgroundLoading: true
                }
            }
        }                                                                    16
  }
Example: binding

> Introducing TaxonSearchController
  – Manages a list of Taxons
   –   Produces a filtered list of Taxons whose names match a substring
   –   Provides a hint for autocompletion
               Eg. you type “He”
               That matches only “Heron ***”
               Hints is “Heron “




                                                                          17
Example: binding

> Some relevant parts of Taxon
public class Taxon
  {
    public-read protected var displayName : String;
    public-read protected var scientificName : String;
    public-read protected var id : String;

      override function toString() {
          return "{displayName} ({scientificName}) ({id})"
        }
  }




                                                             18
Example: binding

> TaxonSearchController (1/3)
public class TaxonSearchController
  {
    public var taxons: Taxon[];
    public var filteredTaxons: Taxon[];
    public var selectedTaxonIndex : Integer = -1; // set from the UI

    public var selectedTaxon = bind
      if (selectedTaxonIndex < 0) then null
                                  else filteredTaxons[selectedTaxonIndex];

    // setting this property will trigger filtering
    public var filter = "" on replace {
        filteredTaxons = taxons[taxon | matches(taxon, filter)];
        updateHint();
      }

    public-read var hint = ""; // the auto-completed hint



                                                                             19
Example: binding

> TaxonSearchController (2/3)
   protected function matches (taxon : Taxon, string: String) : Boolean {
       if (string == "") {
            return true;
         }
       if (taxon.displayName.toLowerCase().startsWith(string.toLowerCase())) {
            return true;
           // more complex in the real case, as it also deals with scientific name
           // but not relevant now
         }
       return false;
     }




                                                                                     20
Example: binding

> TaxonSearchController (3/3)
     protected function updateHint(): Void {
         def hintTry = commonLeadingSubstring(filteredTaxons);
         //
         // Sometimes it can't find a better auto-completion than the current filter,
         //
         hint = if (hintTry.length() > filter.length())
                         then hintTry
                         else filter;
       }

 }




                                                                                        21
Example: binding

> TaxonSearchScreen
public class TaxonSearchScreen extends Container // e.g. a UI panel
  {
    // warning: there are some simplifications, but the concepts are here
    public-read def controller = TaxonSearchController {
         selectedTaxonIndex: bind list.selectedIndex;
      }
    def list = ListBox { // the list widget in the UI
         items: bind controller.filteredTaxons
      };

   def hint = bind controller.hint on replace {
       if (hint != "") {
           filter = hint;
         }
     }
   var resetSelectionWhenFilterChanges = bind controller.selectedTaxon on replace {
       if (controller.selectedTaxon != null) {
           list.selectedIndex = 0;
         }
     }
   TextBox { // a text input box in the UI
       text: bind controller.filter with inverse
     }
   Text { // a label rendering a text in the UI
       content: bind "{sizeof controller.filteredTaxons} specie(s)"                   22
     }
JavaFX mobile in 2009

> Development tools available (emulator, etc...)
> First capable phones at J1: LG Incite, HTC Touch Diamond
> Announced a “JavaFX Mobile player”
  – Would run JavaFX on MSA (JSR 248) JME phones
> Looked very promising
  – That's why blueBill Mobile started with it




                                                             23
Status updates in 2010

> Barcelona Mobile World Congress 2010
  – “Feature phones” (Qualcomm and the Brew Mobile Platform OS)
   –   Sony-Ericsson in “upcoming phones”
> Recent phone status update (JavaFX forums, March 16, 2010)
  – LG Incite, HTC Touch Diamond... still!
   –   “Unofficially” runs on any Windows Mobile 6.x device (e.g. HTC HD2)
> JavaFX 1.3 released on May 14, 2010
  – Shares many improvements with the desktop/web profile
   –   Mobile emulator runs on Mac OS X
> JavaFX Mobile Player MIA (I mean, it's Windows Mobile only)
> No further manufacturer endorsements?
                                                                             24
My view of Java-related mobile solutions

> We're in the climax of the “next generation mobile” war
  – iPhone/iPad, Android leading on the growth rates
   –   Adobe Flash at stake, but if it survives will be relevant
> Is there still room for JavaFX Mobile? JME?
> JavaFX Mobile
   –   Still excites me, technically
   –   The Oracle-Sun deal might have slowed down deals – but...
   –   … timing out this Summer
   –Hoping in a specific Android port/adaptation (using the Android UI/runtime)
               Hearing a few people talking about that
               Would bring-in e.g. the integration with graphic designer workflow
> JME still relevant for the “glorious old guard” (Nokia, etc..., BlackBerry)
  – “Oldies but goodies”                                                         25
My research for the Summer

> Two/threefold strategy
  – Android (it delivers, short time-to-market)
   –   JME (catch the “old glorious guard”)
   –   JavaFX (if industry endorsements / Android support will show up)
> Study whether it is possible to reuse code
  – Not thinking of “catch-all” frameworks
   –   Reusing only the models, writing ad-hoc UIs (no “GCD syndrome”)
   –   Maven for re-using artifacts
   –   Byte-code manipulation (e.g. RetroWeaver) for fitting JME Java 1.3?
   –   Follow my blog and my space at Dzone.com
> The “new” blueBill Mobile for JavaFX code is not yet available
  – Under heavy refactoring, catching up with the Android version            26
Questions?

> Thanks for your attention
  – http://javafx.com
   –   http://bluebill.tidalwave.it/mobile
   –   http://windrose.tidalwave.it




                                             27
Fabrizio Giudici   www.tidalwave.it
Tidalwave s.a.s.   fabrizio.giudici@
                   tidalwave.it

Designing a JavaFX Mobile application

  • 1.
  • 2.
    AGENDA > Putting JavaFX(Mobile) in context > My case study > Main features of JavaFX (Mobile) > JavaFX Mobile through a few examples > Current status of JavaFX Mobile > Conclusion 2
  • 3.
    About the speaker >Fabrizio.Giudici@tidalwave.it > Senior Architect, Mentor, Technical Writer > Working with Java (all the editions, plus exotic stuff) since 1996 > Working with Java ME since 1999 – First J2ME edition at JavaOne™ (Palm) – Developed a couple of research projects with the University of Genoa – Developed a couple of customer projects with my former company – Consulting for customer projects – Author of mobile FLOSS: windRose and blueBill Mobile > Co-leader of JUG Milano 3
  • 4.
    What's wrong withJME > Everything was so good at the old, Palm times – Small devices → no big expectations – One reference platform – One pervasive runtime > With more devices, JME got fragmented – JSR as extension points (e.g. bluetooth, location) – Multiple combinations – Harder and harder to test – windRose pain 4
  • 5.
    Hoping in JavaFX Mobile > JavaFX announced by Sun Microsystems in 2007 – A specific scripting language + a runtime – Fight the “Ugly Java UI stereotype” – Re-designed UI controls – Integration with graphics designers workflow (e.g. NetBeans + Photoshop) – Profiles for multiple targets (desktop / web, mobile) > JavaFX Mobile profile – Can sit on top of JME (MSA: Mobile Service Architecture, a.k.a. JSR-248) – Maybe no more (or reduced) fragmentation? > Oracle commitment after the Sun buy > A fulfilled promise? – Answers at the end :-) 5
  • 6.
    My case study:blueBill Mobile > “The field tool for birdwatchers” – Records geotagged observations – Reference guides with multimedia – Social capabilities (à la Google Friends) > Started in 2009 with JavaFX > Currently the most advanced version is the Android one – JavaFX version being redesigned (more later) > Demo 6
  • 7.
    Main features ofJavaFX > The languages is both imperative and declarative – Imperative: for normal processing – Declarative: mostly for the UI (no XML, no separate layout descriptor)  Binding – Has got closures, mixins > Compiles to the same VM bytecode – Can mix with Java code (e.g. reference a Java library) – On the desktop, can be mixed with Swing (in addition to its own UI widgets) – On the mobile, runs its own UI widgets 7
  • 8.
    Some code examples >Example 1: Calling JME code > Example 2: Posting contents (REST) > Example 3: Getting resources (REST) > Example 4: Binding (if time allows) 8
  • 9.
    Example: calling JMEcode > Not much to say: it just works package it.tidalwave.bluebillmfx; import javax.microedition.location.Criteria; import javax.microedition.location.LocationProvider; import javafx.animation.Timeline; import javafx.animation.KeyFrame; import it.tidalwave.geo.mapfx.model.Coordinates; public class PositionTracker { public-read var currentPosition : Coordinates; def criteria = new Criteria(); var locationProvider : LocationProvider; postinit { locationProvider = LocationProvider.getInstance(criteria); pollingTimeline.play(); } 9
  • 10.
    Example: calling JMEcode > Need to copy data in JavaFX classes if you want binding support function getPosition(): Void { def location = locationProvider.getLocation(5); // JME stuff def coordinates = location.getQualifiedCoordinates(); if (coordinates != null ) { def lat = coordinates.getLatitude(); def lon = coordinates.getLongitude(); currentPosition = Coordinates { latitude: lat; longitude: lon }; } } def pollingTimeline = Timeline { repeatCount: Timeline.INDEFINITE keyFrames: KeyFrame { time: 10s; action: periodicTask } }; function periodicTask(): Void { getPosition(); // update the map position, etc... } } 10
  • 11.
    Example: posting contents >HttpRequest provides a skeleton public class ObservationRDFUpload extends HttpRequest { public-init var serviceURL = "http://myHost:8084/blueBillGeoService"; override public var method = HttpRequest.POST; public var content : String; override var output on replace { if (output != null) { output.write(content.getBytes()); output.close(); } } override function start(): Void { location = "{serviceURL}/postObservation"; // REST call setHeader("Content-Type", "application/rdf"); super.start(); } } 11
  • 12.
    Example: posting contents >HttpRequest provides a skeleton def upload = ObservationRDFUpload { content: // ... assemble the string with the contents }; upload.start(); 12
  • 13.
    Example: reading resources >The relevant model class public class Taxon { public-read protected var id : String; public-read var urlsLoaded = false; var urlsLoading = false; var imageURLs : String[]; // [] is a list (“sequence”), not an array var images : DeferredImage[]; public bound function getImage (index : Integer): DeferredImage { def dummy = loadImageURLs(); // assignment is a requirement for the bound function return images[index]; } public bound function imageCount(): Integer { return sizeof images; } 13
  • 14.
    Example: reading resources >Getting a JSON resource catalog function loadImageURLs(): Boolean { if (not urlsLoading and not urlsLoaded) { urlsLoading = true; def fixedId = clean(id); // get rid of/escape “bad” chars such as :, #, etc... def url = "http://kenai.com/svn/bluebill-mobile~media/catalog/{fixedId}.json"; def request : HttpRequest = HttpRequest { // mere instantiation, not subclassing! location: url; method: HttpRequest.GET; onInput: function (is: InputStream) { // hey, this is not a switch() label! if (request.responseCode != 200) { imageURLs = "media/black_glossy_web20_icon_012.png"; } else { try { def parser = PullParser { documentType: PullParser.JSON; input: is; onEvent: parseEventCallback }; parser.parse(); } catch (e: Exception) { println("Exception {e} while parsing {url}"); } finally { is.close(); 14 } } }
  • 15.
    Example: reading resources >Getting a JSON resource catalog onException: function (e: Exception) { imageURLs = "media/black_glossy_web20_icon_012.png"; urlsLoaded = true; } onDone: function() { images = for (url in imageURLs) { DeferredImage { url: url }; } imageURLs = null; urlsLoaded = true; } } request.start(); } return urlsLoaded; } def parseEventCallback = function (event: Event): Void { if ((event.type == PullParser.TEXT) and (event.name == "url")) { insert event.text into imageURLs; } 15 } }
  • 16.
    Example: reading resources >Loading an image > JavaFX Image loads automatically when initialized var imageCounter = 0; public class DeferredImage { public-init var url : String; public-read var progress = bind image.progress; public-read var error = bind image.error; def id = imageCounter++; public-read var image : Image; public function load() { // start loading only when load() is called if (image == null) { image = Image { url: url backgroundLoading: true } } } 16 }
  • 17.
    Example: binding > IntroducingTaxonSearchController – Manages a list of Taxons – Produces a filtered list of Taxons whose names match a substring – Provides a hint for autocompletion  Eg. you type “He”  That matches only “Heron ***”  Hints is “Heron “ 17
  • 18.
    Example: binding > Somerelevant parts of Taxon public class Taxon { public-read protected var displayName : String; public-read protected var scientificName : String; public-read protected var id : String; override function toString() { return "{displayName} ({scientificName}) ({id})" } } 18
  • 19.
    Example: binding > TaxonSearchController(1/3) public class TaxonSearchController { public var taxons: Taxon[]; public var filteredTaxons: Taxon[]; public var selectedTaxonIndex : Integer = -1; // set from the UI public var selectedTaxon = bind if (selectedTaxonIndex < 0) then null else filteredTaxons[selectedTaxonIndex]; // setting this property will trigger filtering public var filter = "" on replace { filteredTaxons = taxons[taxon | matches(taxon, filter)]; updateHint(); } public-read var hint = ""; // the auto-completed hint 19
  • 20.
    Example: binding > TaxonSearchController(2/3) protected function matches (taxon : Taxon, string: String) : Boolean { if (string == "") { return true; } if (taxon.displayName.toLowerCase().startsWith(string.toLowerCase())) { return true; // more complex in the real case, as it also deals with scientific name // but not relevant now } return false; } 20
  • 21.
    Example: binding > TaxonSearchController(3/3) protected function updateHint(): Void { def hintTry = commonLeadingSubstring(filteredTaxons); // // Sometimes it can't find a better auto-completion than the current filter, // hint = if (hintTry.length() > filter.length()) then hintTry else filter; } } 21
  • 22.
    Example: binding > TaxonSearchScreen publicclass TaxonSearchScreen extends Container // e.g. a UI panel { // warning: there are some simplifications, but the concepts are here public-read def controller = TaxonSearchController { selectedTaxonIndex: bind list.selectedIndex; } def list = ListBox { // the list widget in the UI items: bind controller.filteredTaxons }; def hint = bind controller.hint on replace { if (hint != "") { filter = hint; } } var resetSelectionWhenFilterChanges = bind controller.selectedTaxon on replace { if (controller.selectedTaxon != null) { list.selectedIndex = 0; } } TextBox { // a text input box in the UI text: bind controller.filter with inverse } Text { // a label rendering a text in the UI content: bind "{sizeof controller.filteredTaxons} specie(s)" 22 }
  • 23.
    JavaFX mobile in2009 > Development tools available (emulator, etc...) > First capable phones at J1: LG Incite, HTC Touch Diamond > Announced a “JavaFX Mobile player” – Would run JavaFX on MSA (JSR 248) JME phones > Looked very promising – That's why blueBill Mobile started with it 23
  • 24.
    Status updates in2010 > Barcelona Mobile World Congress 2010 – “Feature phones” (Qualcomm and the Brew Mobile Platform OS) – Sony-Ericsson in “upcoming phones” > Recent phone status update (JavaFX forums, March 16, 2010) – LG Incite, HTC Touch Diamond... still! – “Unofficially” runs on any Windows Mobile 6.x device (e.g. HTC HD2) > JavaFX 1.3 released on May 14, 2010 – Shares many improvements with the desktop/web profile – Mobile emulator runs on Mac OS X > JavaFX Mobile Player MIA (I mean, it's Windows Mobile only) > No further manufacturer endorsements? 24
  • 25.
    My view ofJava-related mobile solutions > We're in the climax of the “next generation mobile” war – iPhone/iPad, Android leading on the growth rates – Adobe Flash at stake, but if it survives will be relevant > Is there still room for JavaFX Mobile? JME? > JavaFX Mobile – Still excites me, technically – The Oracle-Sun deal might have slowed down deals – but... – … timing out this Summer –Hoping in a specific Android port/adaptation (using the Android UI/runtime)  Hearing a few people talking about that  Would bring-in e.g. the integration with graphic designer workflow > JME still relevant for the “glorious old guard” (Nokia, etc..., BlackBerry) – “Oldies but goodies” 25
  • 26.
    My research forthe Summer > Two/threefold strategy – Android (it delivers, short time-to-market) – JME (catch the “old glorious guard”) – JavaFX (if industry endorsements / Android support will show up) > Study whether it is possible to reuse code – Not thinking of “catch-all” frameworks – Reusing only the models, writing ad-hoc UIs (no “GCD syndrome”) – Maven for re-using artifacts – Byte-code manipulation (e.g. RetroWeaver) for fitting JME Java 1.3? – Follow my blog and my space at Dzone.com > The “new” blueBill Mobile for JavaFX code is not yet available – Under heavy refactoring, catching up with the Android version 26
  • 27.
    Questions? > Thanks foryour attention – http://javafx.com – http://bluebill.tidalwave.it/mobile – http://windrose.tidalwave.it 27
  • 28.
    Fabrizio Giudici www.tidalwave.it Tidalwave s.a.s. fabrizio.giudici@ tidalwave.it