Saturday, July 30, 2011

Sencha Touch: Using abstraction to handle platform differences

Sencha Touch does an excellent job of abstracting platform differences in the main. One thing I don't like though, is the style they seem to espouse for handling platform differences in application code. For example, this is from  the source of the Kitchen Sink demo:

1:  demos.List = new Ext.TabPanel ({  
2:    items: [{  
3:      title: 'Simple',  
4:      layout: Ext.is.Phone ? 'fit' : {  
5:        type: 'vbox',  
6:        align: 'center',  
7:        pack: 'center'  
8:      },  

What is line 4 doing? Expressing a platform 'exception' using a ternary operator. I don't like that, and if the differences ever eventuate for more than two options; well, it becomes ugly and clumsy very quickly.

So I implemented a simple 'abstract class' driven implementation. I have an example currently that I'm catering for - hooking up a physical back button event handler. For iOS, this does not mean anything, but it does for Android.

So I have a base type that offers a small interface, redacted here, (but which will be extended over time) that platform specific types can extend. It subtypes the Ext.util.Observable type, as events are suspected to be required at some stage. Note that namespace root of "x" is not the real world token.

1:  x.mobile.ui.platformBase = Ext.extend(Ext.util.Observable, {  
2:    constructor: function(config) {  
3:      this.name = config.name;  
4:      x.mobile.ui.platformBase.superclass.constructor.call(this, config);  
5:    },  
6:    deviceZone: function() {  
7:      return "smartphone";  
8:    },  
9:    unexpectedError: function(msg) {  
10:      Ext.Msg.alert('Sorry!', 'Failed to ' + msg, Ext.emptyFn);  
11:    },  
12:    onDeviceBoot: function() {}  
13:  });  

The method of interest is located at line 12. I want to be able to call that and have the current implementation (whatever that might be) execute the correct behaviour - for iOS a NOP (default implementation) and for Android, using PhoneGap to override back button behaviour - which without interference, will exit the application no matter where you are in terms of navigation - which is not classed as friendly!

The Android implementation appears next. Create a namespace for Android support, and override the onDeviceBoot function to to direct the back button event (PhoneGap 0.9.5.1) to a defined handler function.

1:  Ext.namespace('x.mobile.ui.android');  
2:  x.mobile.ui.android.platform = Ext.extend(x.mobile.ui.platformBase, {  
3:    constructor: function(config) {  
4:      x.mobile.ui.android.platform.superclass.constructor.call(this, config);  
5:    },  
6:    onDeviceBoot: function() {  
7:      document.addEventListener("backbutton",  
8:                                x.mobile.ui.android.androidBackKeyHandler, true);  
9:    }  
10:  });  
11:  x.mobile.ui.android.androidBackKeyHandler = function() {  
12:    console.log('Back key usage detected');  
13:    x.mobile.ui.eventHistory.back();  
14:  };  

Lines 11-13 handle the back button depression; line 13 calls an internal implementation of a history stack, which moves to the previous location.

And this is the index.html inline script that informs the current platform 'helper' that we are in device boot mode:

1:  <script charset="utf-8" type="text/javascript">  
2:        document.addEventListener("deviceready", boot, false);  
3:        function boot() {   
4:            x.mobile.ui.platform.onDeviceBoot();  
5:            x.mobile.sencha.launch();  
6:        };  
7:  </script>  

Line 4 calls the relevant function on the x.mobile.ui.platform object, which is set to an instance of a sub type of platformBase, created by internal machinery by a trivial factory.

No comments: