220 likes | 516 Views
Functional aspects of great native mobile apps. http://www.flickr.com/photos/ourcage/8343799386/. Functionality to go from good to great. Why create a native mobile app if you aren't going to use the hardware that is hard or impossible to access in a mobile web app?
E N D
Functional aspects ofgreat native mobile apps http://www.flickr.com/photos/ourcage/8343799386/
Functionality to go from good to great • Why create a native mobile app if you aren't going to use the hardware that is hard or impossible to access in a mobile web app? • Other functional considerations related to creating great apps • We'll continue the conversation about "great apps" in Unit 5.
Accessing phone hardware • E.g., Compass, Geolocation, Accelerometer, Shake, Sound, Camera • Some of these are robustly supported only in certain mobile browsers (e.g., compass in Safari) but available and reliable in native apps • Others are available in most mobile browsers (e.g., geolocation), but more features are available in native apps
Compass varwin = Ti.UI.createWindow({ backgroundColor : "#FFFFFF", title : "test" }); varlbl = Ti.UI.createLabel(); win.add(lbl); win.open(); function display(e) { lbl.text = e.heading ? 'compass:' + e.heading.magneticHeading + ',when:' + (new Date()).getTime() : 'null heading'; // see also the trueHeading property, used along with location tracking } // you can get heading just once with getCurrentHeading() or you can monitor it… Ti.Geolocation.addEventListener("heading", display); // use removeEventListener when done!!
Geolocation function display(e) { lbl.text= e.coords ? 'lat:' + e.coords.latitude + ',lon:' + e.coords.longitude + ',when:' + (new Date()).getTime() : 'null coords'; } Ti.Geolocation.preferredProvider= Titanium.Geolocation.PROVIDER_GPS; Ti.Geolocation.purpose = "CS496"; Ti.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST; // can use lower accuracy Ti.Geolocation.distanceFilter = 10; // can use broader filter if (Titanium.Geolocation.locationServicesEnabled === false) { alert('You need to turn GPS on.'); } else { // to get location just once Ti.Geolocation.getCurrentPosition(display); // to continually get location Ti.Geolocation.addEventListener('location', display); // unregister with Ti.Geolocation.removeEventListener(display) when done with it! }
Accelerometer • When the device isn’t accelerating, it’s more of a “gravity-meter” than an accelerometer var listener = function(e) { lbl.text= 'accel: ' + e.x + ';' + e.y + ';' + e.z; }; Ti.Accelerometer.addEventListener('update', listener); Ti.Accelerometer.removeEventListener('update', listener);
Shake Ti.Gesture.addEventListener('shake', fn); Ti.Gesture.removeEventListener(fn); // No API for just retrieving once (obviously?) // Seems to only detect really strong shakes (?)
Key cautions • Accessing this specialized hardware is very battery-intensive • Option #1: Just retrieve value once • Use setTimeOut every few minutes if needed • Option #2: Register for the event listener • Look into the specialized APIs for filtering events • E.g., "only fire a geolocation event on change >100 meters" • Unregister your event listener as soon as possible
Camera var win = Ti.UI.createWindow({ backgroundColor : "#FFFFFF", title : "test" }); varbtnCamera = Ti.UI.createButton({ title : 'Take picture', top : 20 }); btnCamera.addEventListener('click', function() { Titanium.Media.showCamera({ success : function(event) { varphotoTaken = event.media; if (event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO) { varimgView = Titanium.UI.createImageView({ left : 10, width : 300, height : 300, image : photoTaken }); win.add(imgView); } }, cancel : function(event) { alert('cancel'); }, error : function(event) { alert('error'); } }); }); win.add(btnCamera); win.open();
Other functional considerations • Great apps also have functionality for: • Validating inputs, managing state throughout an application's lifecycle • Taking advantage of relevant specializeduser interface controls and views • Selectively applying platform-specific APIs
Input validation: Assuring that your app's state is initialized from user data correctly • Sadly, form validation is very underdeveloped in Titanium • Best approach is to: • Add labels to your form, for showing error msgs • In your button click handler, check every input (e.g., with regular expressions) • If an input is invalid, set an error message; else, clear error message(s) and continue.
Handling lifecycle events • Apps can be paused and then resumed • For example, if user goes to home screen and then comes back to your app • iOS and Android have slightly different events, but Titanium hides some of this from you • On pause, • Save any state that cannot be lost, release resources • On resume, • Reinitialize from saved state, reinitialize resources
Example of handling lifecycle events Ti.App.addEventListener('pause',function(e) { // call removeEventListener for hardware // save user data to local storage }); Ti.App.addEventListener('resume',function(e) {// reload user data from local storage // call addEventListener for hardware });
You can also detect Android-specific lifecycle events • Detecting your execution environment function isAndroid() { // iOSvs Android return Ti.Platform.name == 'android'; } • Accessing Android-specific events (another video will cover the Android lifecycle in detail) Ti.Android.currentActivity.addEventListener('create', function(e) { // called when the app's current activity (window) is created }); Ti.Android.currentActivity.addEventListener('start', function(e) { … });Ti.Android.currentActivity.addEventListener('resume', function(e) { … }); Ti.Android.currentActivity.addEventListener('pause', function(e) { … }); Ti.Android.currentActivity.addEventListener('stop', function(e) { … });
Speaking of which… • Great apps take selective advantage of functionality that is platform-specific • Yes, this decreases portability • But it has the potential to improve the user experience and benefits given to users • And use specialized user interface controls • That might be rendered in platform-specific ways
Judicious use of specialization • Specialization driven by guidelines & hardware • E.g., guidelines: iOS apps use toolbars to navigate • E.g., hardware: Android phones have 2+ buttons • FYI, you can also specialize images • Depending on platform and screen density (essentially dots per inch) • Customize using Resources subdirectories
ScrollViewRendered nearly the same on iOS & Android var win = Ti.UI.createWindow({ backgroundColor : "#FFFFFF", layout : 'vertical' }); varscrollView = Ti.UI.createScrollView({ contentWidth : 'auto', contentHeight : 'auto', showVerticalScrollIndicator : true, height : Ti.UI.FILL, width : Ti.UI.FILL, layout : 'vertical' }); for (var i = 0; i < 100; i++) scrollView.add(Ti.UI.createLabel({ text : "item " + i, color: '#000000' })); win.add(scrollView); win.open();
PickerSame code, rendered differently on iOS & Android var win = Ti.UI.createWindow({ backgroundColor: "#FFFFFF", layout: 'vertical' }); var picker = Ti.UI.createPicker({ selectionIndicator: true }); var items = []; items[0]=Ti.UI.createPickerRow({title:'OSU'}); items[1]=Ti.UI.createPickerRow({title:'UO'}); items[2]=Ti.UI.createPickerRow({title:'UW'}); picker.add(items); win.add(picker); win.open();
TabGroupSame code, rendered differently on iOS & Android // From the wizard-generated code… //create module instance var self = Ti.UI.createTabGroup(); //create app tabs var win1 = new Window(L('home')), win2 = new Window(L('settings')); var tab1 = Ti.UI.createTab({ title: L('home'), icon: '/images/KS_nav_ui.png', window: win1 }); win1.containingTab = tab1; var tab2 = Ti.UI.createTab({ title: L('settings'), icon: '/images/KS_nav_views.png', window: win2 }); win2.containingTab = tab2; self.addTab(tab1); self.addTab(tab2); You can just use Ti.UI.createWindow(…), FYI
CoverFlowViewOnly available on iOS var win = Ti.UI.createWindow({ backgroundColor : "#FFFFFF" }); varimgs = []; for (var i = 1; i <= 3; i++) imgs.push({ image : 'img' + i + '.jpg', height : '33%', width : '33%' }); var view = Titanium.UI.iOS.createCoverFlowView({ backgroundColor : '#00000', images : imgs, width : Ti.UI.FILL, height : Ti.UI.FILL }); win.add(view); win.open(); http://www.flickr.com/photos/musicbook/3525997685 http://www.flickr.com/photos/kbcool/2226493331 http://www.flickr.com/photos/ranh/2390167998
ToolbarOnly available on iOS var win = Ti.UI.createWindow({ backgroundColor: "#FFFFFF" }); varbtnReview = Titanium.UI.createButton({ title : 'Review', style : Titanium.UI.iPhone.SystemButtonStyle.DONE, }); // systemButton specifies a standard appearance (has nothing to do with behavior) varbtnTrash = Titanium.UI.createButton({ systemButton : Titanium.UI.iPhone.SystemButton.TRASH, }); varbtnCancel = Titanium.UI.createButton({ systemButton : Titanium.UI.iPhone.SystemButton.CANCEL }); varspacer = Titanium.UI.createButton({ systemButton : Titanium.UI.iPhone.SystemButton.FLEXIBLE_SPACE }); vartoolbar = Titanium.UI.iOS.createToolbar({ items : [btnCancel, spacer, btnTrash, spacer, btnReview], top : 0, borderTop : false, borderBottom : true }); win.add(toolbar); win.open();
MenuOnly available on Android var win = Ti.UI.createWindow({ fullscreen: true }); var activity = win.activity; activity.onCreateOptionsMenu= function(e) { varmenu = e.menu; for (var i = 0; i < 4; i++) { varmenuItem = menu.add({ title : "Choice " + i }); menuItem.addEventListener("click", function(e) { alert("Your choice has been noted.") }); } }; win.open();