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
"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:
- Node(Card) Selector (Basic)
- Node(Card) Details (Basic)
Shows the detail of the selected card (in Card Selector) in central area.
- Node(Card) Selector Filters (Basic, TODO)
- Card Deck Builder (TODO)
- Card Composition Hints based on Might Calculation (Advanced)
- 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
(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 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.
- 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)
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)
@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)
_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)
- Event registration (called by platform-specific* adaptive View class(L2)):
_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.
- Event unregistering: not used yet.
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.
- Event submitting:
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)
- Event handling:
@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.
- UI updating:
There are two kinds of methods to feed a generic ComponentModel object to the representative layer:
UINode.attachView(INodeView)
If an adaptive Component instance already exists (as in case of the NodeContent
), ComponentModel can be attached to it.
UINode.createView()
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:
protected static void update(UINode uiData, ImageView rawView) {
if(uiData instanceof UIImageNode){
int width_limit, height_limit;
width_limit = (int)rawView.getFitWidth();
height_limit = (int)rawView.getFitHeight();
rawView.setImage(new Image(Res.getImageUrl("default_image.gif")));
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)
- 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)
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)
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.
_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.
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
<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)
//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.
//\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)
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)
com.thinkalike.generic.viewmodel.WorkareaViewModel _viewModel =
WorkareaViewModel.getInstance();
Property Events Maintenance: @platform-independent ViewModel class(L7)
- Event registration (called by platform-specific* adaptive View class(L2)):
_listenToVM = new PropertyChangeListener(){
@Override
public void onPropertyChanged(PropertyChangeEvent event) {...}
};
_viewModel.addPropertyChangeListener(Constant.PropertyName.Node,
_listenToVM);
- Event unregistering: not used.
- Event submitting:
this.firePropertyChange(Constant.PropertyName.Node, oldValue, _uiNode);
Property Events Handling: @platform-specific* adaptive View class(L2) and adaptive Component class(L4)
- Event handling:
@Override
public void onPropertyChanged(PropertyChangeEvent event) {
if (event.getPropertyName().equals(Constant.PropertyName.Node)){
updateWorkarea((UINode)event.getNewValue());
}
}
- UI updating:
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
):
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)
...
_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.
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:
<target name="setup-staging-area">
...
<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