Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

How to make your Android projects more portable to Windows/MacOS (and vice versa)

4.98/5 (14 votes)
15 Apr 2014LGPL318 min read 54.5K  
Using ThinkAlike, an ongoing Java MVVM framework(employing Android and JavaFX as view layer), to implement a cross-platform HearthStone game card reference and more.
ThinkAlike Android ThinkAlike JavaFX

Table of Contents

Introduction

This article discusses the practicability of using Java and MVVM pattern to implement Android-Desktop cross-platform applications.

I would like to introduce ThinkAlike codebase firstly, in which I have implemented my cross-platform strategy.

  • It's an ongoing Java MVVM framework (employing Android and JavaFX(or SWT, Swing) as the View layer), aiming at easier cross-platform development.
  • Especially, in case you want to keep taking full advantage of Android native SDK while keeping cross-platform possibility, this Java solution works.
  • It's NOT Mono/Xamarin -- .NET solution is nice. But in some cases, Java solution which is Android's native language will be more expected.
  • It's NOT PhoneGap/Cordova -- Hybrid solution still has UI/UX shortcomings to some extent.
  • It's NOT a Productive framework yet (ongoing). Many useful features could be added, such as component-level property binding (whether editable, closeable, validation rules, hotkey etc.). However, it has already verified its feasibility and extensibility through a small-scale paid project, a cross-platform epub viewer (for Android and Windows).

To add some spice to the MVVM framework, some game factors(images) have been introduced to it.
HearthStone, a newly released free-to-play strategy card game from Blizzard Entertainment, will be used as my theme. Just like the game aiming at cross-platform markets, our goal will be a Card Reference Library for Android and Desktop platforms (screenshots are shown above).

I will illustrate the cross-platform transaction flow by implementing how to "Show details of a selected node(card)" step by step. Readers with MVVM experience may go directly to the core content.
Necessary introduction to relevant concepts (MVVM, Android, JavaFX) are covered in Background section. For how to set up development environment, see Environment section. In particular, for readers who doubt if JavaFX applications can be distributed to a platform without JRE installed, see Distribution section.

Background

Cross-platform MVVM solutions referenced, experienced

In design phase, I had referenced iOS webkit, ZK, Xamarin, Android-Binding, PhoneGap and JavaFX. Before that, I had already had project experience in WPF ("Mother" of MVVM. while MVP is genealogically "Father") and Android UI ecosystem. This cross-platform codebase is initially developed to meet real customer requirements -- not large-scale, but quick evolving Android-Desktop projects.

Knowledge required: MVVM, Android, JavaFX

  • MVVM

  • MVVM in WPF MVVM in ThinkAlike

    "M-V-VM" which originates from MVP pattern (Martin Fowler), is Microsoft's specialization of the latter for implementing WPF/Silverlight. WPF's vision of MVVM is recommended and shown in the first figure above. There are also MVVM frameworks other than the WPF: for Java the ZK framework, and for HTML5 AngularJS and KnockoutJS. In fact the demarcation of responsibilities of different layers is a subject of ongoing discussion and exploration.

    You might notice the "ViewModel" squiggly red underlined (OK, I admit that's because of having forgotten to turn off the spelling-check~). That's what the term "VM" represents. The ViewModel layer extracts the "logic" part (use case oriented, platform-independent) out of the View layer, apart from "non-logic" UI/UX codes(size, color etc.). UI/UX developers can implement different View modules to communicate to one ViewModel, even using different GUI technologies (WPF, WinForm etc.).

    The second figure above depicts ThinkAlike's MVVM implementation. By contrast to WPF, ThinkAlike uses IProperty/ICommand event handling interface as the binder between View layer and ViewModel layer. User commands (not the intermediate ui operations) will be interpreted as ICommand events which will be dispatched from the View and handled by the ViewModel. Meanwhile, UI update feedback will be interpreted as IProperty(Change) events which will be fired from the ViewModel and consumed by the View.

    NOTE: Data-binding, as John Gossman (MVVM creator) said, "has advantages and disadvantages". Most of all, our cross-platform solution do not have "cross-platform" UI markup files yet, which also cut down the benefit of data-binding. How to work out compromise between Android's XML UI markup, JavaFX's FXML UI markup, and other markup languages? Oracle promotes to open source JavaFX for iOS and Android, maybe leads to some breakthrough.

    "Component Model" on the right side of the figure refers to Layouts/Controls (Image/Text/ComboBox etc.). A complete library of platform-independent components, if given more resource and encouragement and had been done, would enable developers to instantiate and control platform-specific Layout/Control instances from generic(platform-independent) modules (as Xamarin has done in .NET). However, because of the dilemma of "cross-platform" markup file, benefit of declaring Layouts/Controls in UI markup files can not be enjoyed.

    For more details on MVVM,

  • Android and JavaFX

  • Android and JavaFX are merely used in platform-specific View layer implementation. Those who have experience in both fields may know how they differs in many aspects (e.g. details like how to auto-fit image to its container). However, even if you are not acquainted with Android/JavaFX, you still can read through this article and grasp the MVVM cross-platform process flow.

    Android topics:

    JavaFX topics:

Environment

The following is my development tools inventory:

  • Base: Eclipse/e(fx)clipse 4.2 Juno + JDK 1.7
  • I strongly recommend e(fx)clipse which provides JavaFX tooling for the Eclipse IDE and keeps stable compatibility to other development.
    JDK 1.7 is employed for JavaFX. Android project will degrade to JDK 1.6 compiler compliance level by default.
  • Android SDK + ADT plugin
  • JavaFX SDK (included in JDK 1.7) + JavaFX Scene Builder

Package Division

ThinkAlike is composed of 3 projects, postfixed with _generic (platform-independent), _jfx (for JavaFX), and _android (for Android) respectively.
Asset files, in contrast to platform-specific Resource files, will be managed in the generic package.

(For Eclipse users) Some settings should be applied to the platform-specific projects:

  • Android project
    • Source folder: Project properties > Build Path > Link Source..., links ThinkAlike_generic\src to ThinkAlike_android\src_generic.
    • Assets folder: uses linked source folder as well. links ThinkAlike_generic\assets to ThinkAlike_android\assets.
  • JavaFX project
    • Source folder: Project properties > Java Build Path> Projects> Add..., adds ThinkAlike_generic. If using linked folder, e(fx)clipse will have some problems.
    • Assets folder: finds com.thinkalike.jfx.assets package folder in Eclipse, New > Folder > Advanced, creates linked folder of ThinkAlike_generic\assets\ThinkAlike. Build Path > Include, to include it to JAR on build.

Functional Design

A complete version of Card Reference Library may have features like:

  1. Node(Card) Selector (Basic)
  2. Node(Card) Details (Basic)
  3. Shows the detail of the selected card (in Card Selector) in central area.
  4. Node(Card) Selector Filters (Basic, TODO)
  5. Card Deck Builder (TODO)
  6. Card Composition Hints based on Might Calculation (Advanced)
  7. Automatic competition between Card Decks (Advanced)

No.1-3 are general-purpose features and will be implemented in the ThinkAlike project. Deck Builder can be forked as an independent project in future. Feature#2, showing details of a card, will be illustrated in this article.

Architectural Analyse and Design

Sequence diagram

Sequence Diagram of ThinkAlike MVVM (Click to enlarge)
(Click to enlarge)

Details will be illustrated in plain text in Implementation section.

Glossary

To avoid ambiguity, usage of the following terms will be clarified:

  • platform-specific: also known as "platform-dependent". Refers to a feature/model that is linked to a specific technological platform, such as Android, JavaFX.
  • platform-independent: as opposed to platform-specific.
  • generic: refers to platform-independent. Platform-independent codes/assets are assembled in a generic project.
  • View: refers to a Window-level view. Android uses the term Activity, whereas JavaFX names it as Scene.
  • Component: refers to Layout or Control, such as LinearLayout/TextView/ImageView in Android or HBox/VBox/Label/ImageView in JavaFX.
  • Node: To avoid using domain-specific terms (e.g.Card) in a framework, general-purpose term Node will be used.

Implementation (Feature#2)

Now, let's walk through the steps required to implement the Feature#2 -- showing details of a node(card).

Sequence diagram and definition of terms can be found at the previous section.

[JavaFX]

I prefer to build JavaFX project first so as to take advantage of its swiftness in debug. When you need to confirm Android features, you may switch priority as you wish.

0.Fix the classes/instances illustrated in the sequence diagram, especially platform-specific ones:

  • L0: Platform-specific "adaptive" Application class: com.thinkalike.jfx.<span style="color: blue;">ThinkAlikeApp</span>
  • Entrance of an application. It inherits native Application class (javafx.application), and implements platform-independent Platform interface (so as to provide platform-specific data/method to the generic modules)
  • L1: Platform-specific native View classes: e.g. javafx.scene.layout.<span style="color: blue;">AnchorPane</span>
  • Underlay native class of the main window.
  • L2: Platform-specific "adaptive" View classes: e.g. com.thinkalike.jfx.view.<span style="color: blue;">MainScene</span>
  • Inherits L1 for native UI functionalities, and interacts with L7 (ViewModel class, described below) through the IProperty/ICommand event handling interface.
  • L3: Platform-specific native Component classes: e.g. javafx.scene.image.<span style="color: blue;">ImageView</span>
  • Underlay native class of layouts/controls, including image view showing the node(card).
  • L4: Platform-specific "adaptive" Component classes: e.g. com.thinkalike.jfx.control.<span style="color: blue;">ImageNodeView</span>
  • Inherits L3 for reusing native functionalities, while meets requirements of L5 (Component interface, described below) so as to update the underlying Component view based on data of ComponentModel instance (root class is UINode).
  • L5: Platform-independent Component interface: e.g. com.thinkalike.generic.viewmodel.control.<span style="color: blue;">IImageNodeView</span>
  • Declares requirements an "adaptive" Component classes must meet, generally a update(UINode) interface.
  • L6: Platform-independent ComponentModel classes: e.g. com.thinkalike.generic.viewmodel.control.<span style="color: blue;">UIImageNode</span>
  • Includes UI related data/method. Some UINode may hold a readonly "facade" (base interface is INodeRO) of the background data object.
  • L7: Platform-independent ViewModel classes: e.g. com.thinkalike.generic.viewmodel.<span style="color: blue;">WorkareaViewModel</span>
  • Usually singleton, and instantiated by related L2 ("adaptive" View class). It comprises implementation of the IPropery(including event regist/unregist/submit), and transaction of the ICommand(retrieving data object, encapsulating view object, and submitting IProperty change event to all listeners alive).
  • L8: Platform-independent data classes: e.g. com.thinkalike.generic.domain.<span style="color: blue;">ImageNode</span>
  • Includes Domain related data/method, and usually limits its accessibility within ViewModel layer, or more restrictedly, (Domain)Model layer.
  • L9: Platform-independent DAO classes: e.g. com.thinkalike.generic.viewmodel.<span style="color: blue;">NodeLoader</span>
  • Converts between persisted data and data objects. Its accessibility is also limited to ViewModel layer and (Domain)Model layer.

1.First (and the most intuitive) step, is to scribble the non-logic UI of the use story (Feature#2)

UI markup file - JavaFX

  • UI Layout: \src\com\thinkalike\jfx\res\layout\scene_main.fxml
  • Above is code segment regarding UI area of the Feature#2 (named as "Work Area"). It should be placed in a FXML file which declares the whole layout of the View (L2). Markup language of FXML is easy to learn for those who have experience in HTML/XAML/Android-XML markup. For a quick start at JavaFX UI programming (Java or markup), you can refer to Knowledge required sub-section.

    fx:id="inv_nodeContent" identifies the image element which shows the detail of the node(card) selected by user. Note that the ImageNodeView is not a JavaFX native class but an "adaptive Component class"(L4). JavaFX allows customized Layout/Control classes being employed directly in UI markup files (as Android do), enabling thorough work division between non-logic UI/UX and logic developers.

  • UI Style: \src\com\thinkalike\jfx\res\style_dark.css (dark style)
  • JavaFX abundantly takes advantage of Cascading Style Sheets (CSS). Web developers having W3C CSS knowledge can easily grasp its JavaFX equivalents prefixed with a vendor decor "-fx-". (Yes, it's easier to me than that of the Android.)

2.Associate the platform-specific* adaptive View class(L2) to its corresponding platform-independent ViewModel class(L7) for handling IProperty change events.

SD_ThinkAlike_MVVM_IProperty

  • IProperty events to be handled: node(card) in the middle of the WorkArea has been changed.
  • Instantiation of platform-specific* adaptive View class: @(takes place in) platform-specific* adaptive Application class(L0)
  • Java
    //com.thinkalike.jfx.ThinkAlikeApp
    com.thinkalike.jfx.view.MainScene = 
            (MainScene) replacePrimarySceneContent(Res.getLayoutUrl("scene_main.fxml"));

    MainScene is the host adaptive View (Scene means Activity in JavaFX). URL of the FXML file is taken to instantiate it.

    com.thinkalike.jfx.res.Res is a platform-specific utility class. Resource/Asset management code is beyond the main thread of this article however, you may check source package for details.

  • Instantiation of platform-specific* adaptive Component class: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.jfx.view.MainScene
    @FXML
    com.thinkalike.jfx.control.ImageNodeView inv_nodeContent;

    In JavaFX, view classes declare their components(fields), those marked up in related FXML, as instance variables in this way. The inv_nodeContent will then be assigned to the component with the same name(fx:id="inv_nodeContent") automatically when FXML being parsed.

  • Instantiation of (Reference to) platform-independent ViewModel class: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.jfx.view.MainScene
    _vm_workarea = com.thinkalike.generic.viewmodel.WorkareaViewModel.getInstance();

    The true core of cross-platform MVVM project, ViewModel class, is usually instantiated as singleton by one of its responsive View instances, and has a longer life cycle.

  • Property Events Maintenance: @platform-independent ViewModel class(L7)
    1. Event registration (called by platform-specific* adaptive View class(L2)):
    2. Java
      //com.thinkalike.jfx.view.MainScene
      _listenToVM_workarea = new PropertyChangeListener(){
          @Override
          public void onPropertyChanged(PropertyChangeEvent event) {...}
      };
      _vm_workarea.addPropertyChangeListener(Constant.PropertyName.Node, 
                                              _listenToVM_workarea);

      _listenToVM_workarea is instantiated to handle a simple onPropertyChanged() callback interface. It then subscribes to the concerned ViewModel(_vm_workarea), listening to events about a particular property. Constant.PropertyName.Node identifies the property and has no duplicates in ViewModel base. For heavy-weight application, EventBus pattern will be useful.

    3. Event unregistering: not used yet.
    4. For more implementation details, there is a PropertyChangeListenerAdapter managing a WeakReference map to avoid obsolete event listeners being notified. You may call removePropertyChangeListener() intentionally to cancel event subscription as well.
    5. Event submitting:
    6. Java
      //com.thinkalike.generic.viewmodel.WorkareaViewModel
      this.firePropertyChange(Constant.PropertyName.Node, oldValue, _uiNode);

      When ViewModel have prepared a new view object or a ComponentModel (e.g.UIImageNode), it submits property change event with the object as argument.

  • Property Events Handling: @platform-specific* adaptive View class(L2) and adaptive Component class(L4)
    1. Event handling:
    2. Java
      //com.thinkalike.jfx.view.MainScene
      @Override
      public void onPropertyChanged(PropertyChangeEvent event) {
          if (event.getPropertyName().equals(Constant.PropertyName.Node)){
              updateWorkarea((UINode)event.getNewValue());
          }
      }

      When being notified that Node has changed, adaptive View class will convert event arguments into inner method arguments.

    3. UI updating:
    4. There are two kinds of methods to feed a generic ComponentModel object to the representative layer:

      1. UINode.attachView(INodeView)
      2. If an adaptive Component instance already exists (as in case of the NodeContent), ComponentModel can be attached to it.
      3. UINode.createView()
      4. Otherwise, if there is not an adaptive Component, ComponentModel can ask for dynamic instantiation of the relevant type of Component class (as in case of the cells in NodeSelector). Factory class will instantiate appropriate platform-specific* Component class on demands.

      In either case, an instance of adaptive Component class (L4) will call update(UINode) to update itself. At last, the underlying native Component class (L3) will accept translated info from ComponentModel to do actual rendering. In this regards, ComponentModel (belongs to the generic module) SHOULD be designed to be able to accommodate different needs of platform-specific View classes. For example, in case of ImageView, Android and JavaFX has different procedure on relative dimension measurement (JavaFX doesn't have an onPreDraw() callback) and on async image loading (JavaFX supports it by default, while Android doesn't). So developers have to research strategies of each platform to find a proper way out. Gladly, such work need to be done only once (for each kind of components, and different non-functional requirements). For JavaFX:

      Java
      //com.thinkalike.jfx.control.ImageNodeView
      protected static void update(UINode uiData, ImageView rawView) {
          if(uiData instanceof UIImageNode){
              //0.initialize according to context.
              int width_limit, height_limit;
              width_limit = (int)rawView.getFitWidth();
              height_limit = (int)rawView.getFitHeight();
              
              //1.set the default image: 
              rawView.setImage(new Image(Res.getImageUrl("default_image.gif")));
      
              //2.Async load Image: 
              final String imageUrl = Util.getAbsoluteUrl(((UIImageNode)uiData).getRelativePath());
              Image image = Util.decodeThumbFromFile(imageUrl, width_limit, height_limit);
              rawView.setImage(image);
          }
      }

3.Make platform-specific* adaptive View class(L2) translate user's command operation to ICommand event which is understandable to the corresponding platform-independent ViewModel class(L7)

SD_ThinkAlike_MVVM_ICommand (Click to enlarge)

  • ICommand events to be activated: selected node(card) in the NodeSelector has been changed.
  • User's command operation: selected node(card) in the NodeSelector area (left side of the window) has been changed.
  • ICommand event activation: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.jfx.view.MainScene
    this.lv_nodeList.getSelectionModel().selectedItemProperty().addListener(
            new ChangeListener<UINode>() {
                public void changed(ObservableValue<? extends UINode> ov, 
                        UINode old_val, UINode new_val) {
                    if(_vm_nodeSelector!=null){
                        _vm_nodeSelector.onNodeSelected(new_val);
                    }
                }
            });

    When user interaction happens at the NodeSelector, the above ChangeListener will receive notification from native ListView control (lv_nodeList), and then activates ICommand(onNodeSelected()) of the related ViewModel(_vm_nodeSelector : NodeSelectorViewModel). As mentioned above, an EventBus pattern can be employed here for both IProperty and ICommand events. In simplified cases, however, a synchronized method call works as well.

  • ICommand event handling: @platform-independent ViewModel class(L7)
  • Java
    //com.thinkalike.generic.viewmodel.NodeSelectorViewModel
    public void onNodeSelected(UINode uiNode){
        INodeRO oldValue = _nodeSelected_RO;
        _nodeSelected_RO = uiNode.getDataRO();
        this.firePropertyChange(Constant.PropertyName.Node, oldValue, _nodeSelected_RO);
    }

    On accepting ICommand event, NodeSelectorViewModel retrieves a "readonly data interface" (INodeRO) from the UINode. That's because UINode, as a view object, has its own UI context which cannot be simply reused by other UI (for example, in this case, node in a list cell will have a smaller default size than that in the work area), and therefore has to expose underlying data object for transfer. The INodeRO, or simply Node if access privilege is not considered, will be used to submit an IPropery change event in this case.

    Java
    //com.thinkalike.generic.viewmodel.WorkareaViewModel
    _listenToVM_nodeSelector = new PropertyChangeListener(){
        @Override
        public void onPropertyChanged(PropertyChangeEvent event) {
            thisInstance.setNodeRO((INodeRO)event.getNewValue());
        }
    };
    ...
    private void setNodeRO(INodeRO nodeRO){
        Object uiContext = Loader.getInstance().getPlatform().getUIContext();
        if(nodeRO instanceof ImageNode.RO){
            ImageNode node = new ImageNode(((ImageNode.RO)nodeRO).getRelativePath()); 
            UIImageNode uiNode = new UIImageNode(uiContext, node, false); 
            setUINode(uiNode);
        }
        else if(nodeRO == null){
            setUINode(null);
        }
    }

    WorkareaViewModel listens to the NodeSelectorViewModel, receives a readonly interface of the real data object, creates temporary data object and view object representing the node(card) on focus, and recycles the old one.

4. Debug, research details of native API, tune, and reach the goal.

ThinkAlike JavaFX

Til now, Feature#2 in the JavaFX project has been completed. Whether or not this architecture stands up to platform migration can be tested in the development of Android project.

[Android]

Development steps resemble the previous ones (JavaFX's) to a large extent, which proves the benefit of cross-platform solution. Therefore I only need to replace and specify the changed part.

0.Fix the classes/instances illustrated in the sequence diagram

  • L0: Platform-specific "adaptive" Application class: com.thinkalike.android.<span style="color: blue;">ThinkAlikeApp</span>
  • L1: Platform-specific native View classes: e.g. android.support.v4.app.<span style="color: blue;">Fragment</span>
  • Fragment in Android enables self-adaptive layout for different mobile devices.
  • L2: Platform-specific "adaptive" View classes: e.g. com.thinkalike.android.view.<span style="color: blue;">WorkareaFragment</span>
  • L3: Platform-specific native Component classes: e.g. android.widget.<span style="color: blue;">ImageView</span>
  • L4: Platform-specific "adaptive" Component classes: e.g. com.thinkalike.android.control.<span style="color: blue;">ImageNodeView</span>
  • L5: Platform-independent Component interface: e.g. com.thinkalike.generic.viewmodel.control.<span style="color: blue;">IImageNodeView</span>
  • L6: Platform-independent Component classes: e.g. com.thinkalike.generic.viewmodel.control.<span style="color: blue;">UIImageNode</span>
  • L7: Platform-independent ViewModel classes: e.g. com.thinkalike.generic.viewmodel.<span style="color: blue;">WorkareaViewModel</span>
  • L8: Platform-independent data classes: e.g. com.thinkalike.generic.domain.<span style="color: blue;">ImageNode</span>
  • L9: Platform-independent DAO classes: e.g. com.thinkalike.generic.viewmodel.<span style="color: blue;">NodeLoader</span>

No doubt, L5-L9, these 5 levels will cost ZERO in platform migration.

1.Implement the non-logic UI of the use story (Feature#2)

  • UI Layout: \res\layout\workarea.xml
  • XML
    <LinearLayout
        android:id="@+id/ll_work_overlay"
        android:gravity="center_horizontal"
        android:orientation="vertical">
        ...
        <com.thinkalike.android.control.ImageNodeView
            android:id="@+id/iv_nodecontent"
            ... />
        ...
    </LinearLayout>

    Android uses XML in UI markup files. android:id="@+id/iv_nodecontent" identifies the image control corresponding to node(card) detail.

    ImageNodeView is an adaptive Component class(L4) parallel to that in JavaFX.

  • UI Style: N/A. Comparatively, JavaFX has shortened learning curve in this field.

2.Associate the platform-specific* adaptive View class(L2) to its corresponding platform-independent ViewModel class(L7) for handling IProperty change events.

  • IProperty events to be handled: node(card) in the middle of the WorkArea has been changed.
  • Instantiation of platform-specific* adaptive View class: @(taken place in) platform-specific* adaptive Application class(L0)
  • XML
    //AndroidManifest.xml
    <application
        android:name=".android.ThinkAlikeApp">
        <activity
            android:name=".android.view.MainActivity"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    Android developer knows, instantiation of Activity classes (associated with Intent) and that of the Application class is based on the manifest info of AndroidManifest.xml, which is different to JavaFX application.

    XML
    //\res\layout\activity_main_twopane.xml
    <LinearLayout
        android:id="@+id/ll_twopane">
        <fragment
            android:id="@+id/nodeselector"
            android:name="com.thinkalike.android.view.NodeSelectorFragment"
            ... />
        <fragment
            android:id="@+id/workarea"
            android:name="com.thinkalike.android.view.WorkareaFragment"
            ... />
    </LinearLayout>

    The direct parent window in Feature#2, however, is WorkareFragment, as designed in a UI markup file.

  • Instantiation of platform-specific* adaptive Component class: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.android.view.WorkareaFragment
    com.thinkalike.android.control.ImageNodeView _inv_nodecontent = 
            (ImageNodeView) rootView.findViewById(R.id.iv_nodecontent);
  • Instantiation of (Reference to) platform-independent ViewModel class: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.android.view.WorkareaFragment
    com.thinkalike.generic.viewmodel.WorkareaViewModel _viewModel = 
            WorkareaViewModel.getInstance();
  • Property Events Maintenance: @platform-independent ViewModel class(L7)
    1. Event registration (called by platform-specific* adaptive View class(L2)):
    2. Java
      //com.thinkalike.android.view.WorkareaFragment
      _listenToVM = new PropertyChangeListener(){
          @Override
          public void onPropertyChanged(PropertyChangeEvent event) {...}
      };
      _viewModel.addPropertyChangeListener(Constant.PropertyName.Node,
                                  _listenToVM);
    3. Event unregistering: not used.
    4. Event submitting:
    5. Java
      //com.thinkalike.generic.viewmodel.WorkareaViewModel
      this.firePropertyChange(Constant.PropertyName.Node, oldValue, _uiNode);
  • Property Events Handling: @platform-specific* adaptive View class(L2) and adaptive Component class(L4)
    1. Event handling:
    2. Java
      //com.thinkalike.android.view.WorkareaFragment
      @Override
      public void onPropertyChanged(PropertyChangeEvent event) {
          if (event.getPropertyName().equals(Constant.PropertyName.Node)){
              updateWorkarea((UINode)event.getNewValue());
          }
      }
    3. UI updating:
    4. As discussed in JavaFX implementation, how an adaptive Component class (L4, e.g.ImageNodeView) translates the ComponentModel (UIImageNode) and feeds it to the native Component class (L3, e.g.ImageView) depends on the platform-specific* GUI API interface. Android has OnPreDrawListener.onPreDraw() which can be used to retrieve parent view's dimension before actually rendering an ImageView. This transaction is encapsulated into the adaptive Component class (ImageNodeView):

      Java
      //com.thinkalike.android.control.ImageNodeView
      final ImageNodeView thisInstance = this;
      ...
      @Override
      public boolean onPreDraw() {
          thisInstance.getViewTreeObserver().removeOnPreDrawListener(this);
          int width_image = Util.getActualLayoutWidth(thisInstance);
          int height_image = Util.getActualLayoutHeight(thisInstance);
          MediaAsyncLoader.asyncLoadImageFile(imagePath, width_image, height_image, _onMediaLoadListener);
          return true;
      }

3.Make platform-specific* adaptive View class(L2) translate user's command operation to ICommand event which is understandable to the corresponding platform-independent ViewModel class(L7)

  • ICommand events to be activated: selected node(card) in the NodeSelector has been changed.
  • User's command operation: selected node(card) in the NodeSelector area (left side of the window) has been changed.
  • ICommand event activation: @platform-specific* adaptive View class(L2)
  • Java
    //com.thinkalike.android.view.NodeSelectorFragment
    ...
    _lv_nodeList.setOnItemClickListener(this);
    ...
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        assert(parent.getAdapter() instanceof NodeAdapter);
        NodeAdapter adapter = (NodeAdapter)parent.getAdapter();
        UINode uiNode = (UINode)adapter.getItem(position);
        if(_viewModel!=null)
            _viewModel.onNodeSelected(uiNode);
    }

    Similar to the JavaFX project, user's command operation will happen at a native ListView control (_lv_nodeList), and then activates ICommand(onNodeSelected()) of the related ViewModel(_viewModel : NodeSelectorViewModel).

  • ICommand event handling: @platform-independent ViewModel class(L7)
  • The story of how NodeSelectorViewModel accepts ICommand request, transforms the parameter, and dispatches it to its listener WorkareaViewModel is just same as that in the JavaFX project.
    Platform-independent code needn't to be updated as long as the UI logic keeps true.

4.Debug, research details of native API, tune, and reach the goal. ThinkAlike Android

Distribution

Specifically, distributing a JavaFX application to a platform innocent of JRE/JDK is confirmed to be possible.

Here is a must-read for Eclipse users on cross-platform distribution of JavaFX project (for Windows and MacOS):
JavaFX 2 Tutorial Part VII - Deployment With E(fx)clipse
In ThinkAlike project, because "linked source folder" is used and cannot be resolved by Ant (the default one in the Eclipse), some manual amendment should be made in the build.xml:

XML
<target name="setup-staging-area">
    ...
    <!-- Linked Source Folder. Need to copy them manually -->
    <mkdir dir="project/src/com/thinkalike/jfx/assets" />
    <copy todir="project/src/com/thinkalike/jfx/assets">
        <fileset dir="${project.loc}\..\ThinkAlike_generic\assets-en">
            <include name="/**" />
        </fileset>
    </copy>
    ...
</target>
project.loc is an Ant property you should add to Eclipse preferences (Preferences > Ant > Runtime > Properties tab) with the value of ${project_loc} (predefined variable of Eclipse).

Deployment on MacOS (HearthStone supports MacOS X as well) has only been tested by installing the official JavaFX Ensemble sample application. Any efforts of testing ThinkAlike on the MacOS will be really appreciated.

Conclusion

So in this article, I discussed possibility of implementing an Android-Desktop cross-platform application by using MVVM, Android SDK and JavaFX SDK. A Card Reference Library has been built up, which hopefully can be used when you enjoy the HearthStone online game.

Source of ThinkAlike is available at: https://github.com/tiancheng2000/ThinkAlike
Official site of HearthStone card game: http://us.battle.net/hearthstone/en/

For future improvements: 1.improving framework interface 2.more platform-independent Component classes, and being instantiated from generic code 3.EventBus.

The card game is free, the framework is free, while the pleasure of creation and problem solving will be priceless :)

Acknowledgement

This is my first post to CodeProject. Thanks for community editors' kindly help on both technological and language perspectives! Thanks also given to my wife, who has no IT language knowledge but supports me and "debugs" my English grammar. No matter which languages we speak, "native" or not, I hope that on interesting things we "Think Alike".

History

03-22-2014: 1st edition

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)