Noodlecode parody of spaghetti code

Tag: titanium mobile (page 1 of 2)

Webview evalJS return null in Android 4.2.2

I have a webview that points to external URL

var webview = Ti.UI.createWebView({
	url: 'http://example.com/test.html'
});

In test.html I have a global variable

<script type="text/javascript">
var myvar = JSON.stringify({ status: true });
</script>

On webview onload event, evalJS('myvar') return null, and inside log message got “Timeout waiting to evaluate JS”. After a few days try & error, test and compare, finally I found the culprit is within tiapp.xml file.

My tiapp.xml has a customized <android> section

<android xmlns:android="http://schemas.android.com/apk/res/android">
  <tool-api-level>17</tool-api-level>
  <manifest android:installLocation="auto" android:versionCode="1" android:versionName="1.0.0">
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
    <application>
      <activity android:configChanges="keyboardHidden" android:name="org.appcelerator.titanium.TiActivity" android:screenOrientation="portrait"/>
    </application>
  </manifest>
  <services>
    <service type="standard" url="lib/service/clear.js"/>
  </services>
</android>

It turns out that <uses-sdk> part is the one causes this bug. Removing the line <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/> fix this bug and I can retrieve myvar values using evalJS() again.

Logcat

To use logcat to debug Android app

  1. Install Android SDK
  2. Add c:\Android\android-sdk\platform-tools\adb.exe to PATH

Command:

  • get list of connected devices, will return device serial number
    adb devices
  • filter logcat by tag (e.g by “TiAPI” tag)
    adb -s <DEVICE_SERIAL_NUM> "TiAPI"

Load nib file in Titanium module iOS

When creating Titanium module for iOS platform, sometimes we need to include xib (NeXT Interface Builder) file. But if we include the file just like that, then we will face this error

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/username/Library/Application Support/iPhone Simulator/6.1/Applications/<app id>/myiosmodule.app> (loaded)' with name 'MyView''

To fix, we need to include a compiled xib file known as nib file. To compile it, you need to include it in a native iOS project & build it.

Update: Easier way to compile xib to nib file is using ibtool

$ ibtool MPAdBrowserController.xib --compile MPAdBrowserController.nib

Compile xib to nib file

For example here, I’m create mopub module for iOS

  1. Open Xcode, create new project, and choose ‘Single View Application’
    new-project-option
  2. Add xib file to the project
    add-file
    select-xib-file
  3. Make sure it is included in the ‘Copy Bundle Resource’ list, then click ‘Run’ button to launch the sample app in simulator
    copy-bundle-run
  4. After it runs on simulator, using Finder go to /Users/username/Library/Application Support/iPhone Simulator/6.1/Applications/<app id>/test.app, then see package content
    open-simulator-folder-2
    show-package-content
  5. You’ll find the nib file there. Put this nib file into /assets/ folder of TItanium module. Then you need to edit the path to load nib file in initWithNibName method.
    edit-code-init

In case you can’t edit the source Objective-C file, you have to put the nib file into /Resources/iphone/ folder of the app project

Enable x86 libs in production build Titanium app

In Titanium studio when we build an apk for production, it doesn’t include x86 libs which cause this exception occur on users device, and the app force close immediately after launch:

KrollRuntimeThread msg:java.lang.UnsatisfiedLinkError: Couldn't load stlport_shared: findLibrary returned null;

There’s a line of code in Titanium build script build.py that disable it in production settings, which the reason is unknown (last time it was because there are not many x86 devices out there, but it’s different now).

To enable x86 libs in production, comment out this line of code from build.py (mobilesdk/<os>/<version>.GA/android/build.py)

for abi in self.abis:
  lib_source_dir = os.path.join(sdk_native_libs, abi)
  lib_dest_dir = 'lib/%s/' % abi
# if abi == 'x86' and ((not os.path.exists(lib_source_dir)) or self.deploy_type == 'production'):
#   # x86 only in non-production builds for now.
#   continue

Source: http://developer.appcelerator.com/question/154006/x86-libs-not-included-in-production-apk

Titanium iOS “build” is an unrecognized command

Was trying to build an iOS app using sdk 3.1.2 but got this error.

[ERROR] "build" is an unrecognized command

When running titanium build command on the project folder, I realize that only Titanium CLI was updated to version 3.1.2 while its SDK still in 3.1.1

Updating the SDK solve this problem, by running this command

titanium sdk install 3.1.2.GA

Indirect code execution flow

Intro: we have an Android app, with typical main and detail window structure.

Let’s see this situation: when user tap a button on main window and then open a detail window, later when user closes the detail window, it needs to update the main window UI.

Here, we have two method of how to update the UI – proactive / direct way, or passive / indirect way.

First, there are several point during detail window closing process, which we can run the function to update the main window UI.

  • when user press back button on detail window
  • when detail window close event
  • when main window resumed

Direct way – when running the function to update main window UI during the 1st and 2nd point above.

// in detail window class

detailWindow.addEventListener('androidback', function() {
	mainWindow.updateUI();
	detailWindow.close();
});

// or

detailWindow.addEventListener('close', function() {
	mainWindow.updateUI();
});

Some disadvantages for this method:

  • detail window need to keep a reference to main window.
  • if updateUI() function take some time, then closing detail window will be delayed

Therefore, to ensure responsiveness of the app, we need to utilize indirect way of executing the code. When main window resume, we run the code to update the UI. But, what if we need to update the UI only when user close the detail window? Here, we need a state variable (variable that keeps track of a state).

// in main window class

var detailWindowOpened = false;
btn.addEventListener('click', function() {
	detailWindowOpened = true;
	detailWindow.open();
});

detailWindowOpened is the state variable, it tracks whether detail window has been opened or not.

// in main window class

mainWindow.activity.addEventListener('resume', function() {
	if (detailWindowOpened) {
		updateUI();
		detailWindowOpened = false;
	}
});

Then, when main window resume, it will automatically update its UI. So here, we don’t need to keep a reference of main window inside detail window class, and closing detail window won’t be delayed.

Titanium app components interaction using events and callbacks

Events

Events are used to broadcast an action happened to zero or many receipients.

Example, in a list of items, when user click on ‘Load more’ button at the bottom of the list, at least 2 actions need to be executed simultaneously – show the loading indicator and start fetching more data from web service.

var btn = Ti.UI.createButton({ title: 'Load more' });
btn.addEventListener('click', function() {
    webservice.getMoreData();
});
btn.addEventListener('click', function() {
    loadingIndicator.show();
});

Another example is, when the loading data process is done, an event is being broadcasted.

webservice.addEventListener('data_received', function(data) {
    if (win) {
        renderData(data);
    }
});

Here, if the current window is still opened, at least one receipient will respond to that event, or else there’s no receipient at all.

Callbacks

Callbacks are used as an immediate respond to be called right after a block of code has done executing. Callbacks can only be attached to only one function call – only have one receipient that respond to it. Callbacks are suitable to use on a function call that will return result asynchronously, but return response differently everytime.

Example, when calling a function to retrieve ID3 tag info from an MP3 file, a callback is passed when the retrieve process is done.

var ID3 = require('module.id3tag');

ID3.getInfo({
	file: Ti.Filesystem.getFile('song.mp3'),
	success: function(e) {
		Ti.API.info('Song artist: ' + e.artist);
	}
});

Object oriented Javascript with CommonJS in Titanium app

Class definition

// class definition + commonjs module
function ClassName() {
    // code...
}
module.exports = ClassName;

Use normal javascript function as class definition. Follow certain class / function naming convention to make sure you are not confused between a class or a function. For example, class name use Pascal case (ClassName), while function name use camel case (isFunctionName).

Public / private methods

By utilising the scope of function or variable definition, we can make that function/variable to be public or private

function ClassName() {

	// public method
	this.getData = function() {}
	
	// private method
	function processData() {}

}

After instantiating ClassName, we can invoke public method on the object. Private method will never be accessible outside of the class scope

var cls = new ClassName();
cls.getData();

Public / private properties / variables

function ClassName(args) { // `args` will be private variable

	// private properties/variables
	var pageNum = 1;
	
	// public properties
	this.version = 1.0;

	// getter
	this.getPageNum = function() {
		return pageNum;
	};
	
	// setter
	this.setPageNum = function(num) {
		pageNum = num;
	};
	
}

Similar to public method, only public properties are accessible from the object created. Developer can use getter/setter method to access private properties. Getter/setter method is useful if you want to keep private variables from being modified outside of the class.

It’s a good practice to use getter/setter on object properties, because we can control who can modify the object properties and when it is modified by external component, we can customize the behavior.

// for example, here is method set the page number of an object.
// in this setter method, we can customize the behavior in case 
// the external component provide invalid input
this.setPageNum = function(num) {
	if (num < 1) {
		num = 1;
	}
	if (num > maxNum) {
		num = maxNum;
	}
	pageNum = num;
}

Static method/properties

ClassName.getName = function() {};
ClassName.appName = 'test';

// static properties can also used as constant
ClassName.APP_ID = 'xxx';

Inheritance

There are no direct way to achieve class inheritance similar to what Java, Python or other languages that have true OOP feature. In Javascript however, we can extend an object functionality by defining it in another class.

function NewClassName() {

	var self = new ClassName();
	
	// extend object with new method
	self.processSomething = function() {};
	
	return self;

}

Another way to customize a class is that class must provide some kind of abstraction. So that when instantiating the class, we can pass some arguments to it to modify its behavior (kind of polymorphism).

function AnotherClassName() {
	
	var self = new ClassName({
		id: 1,
		name: 'abc',
		onClick: onClick
	});
	
	function onClick() {
		// code...
	}
	
	self.doSomething = function() {};

	return self;
	
}

// using the child class, we still can invoke methods from parent class
var acn = new AnotherClassName();
acn.getData();
acn.doSomething();

Titanium ListView

Concept behind new Titanium ListView is similar to ModelViewViewModel (MVVM) pattern, where you have a set of data and you will map them to a view template

Let’s say you have this kind of data structure (model)

Person
- id {Number}
- name {String}
- photo {String} - URL
- message {String}

And you have an array of Person objects

var dataModels = [
  {
    id: 1,
    name: 'John',
    photo: 'http://example.com/img/john.jpg',
    message: 'Thank you'
  },
  {
    id: 2,
    name: 'Mary',
    photo: 'http://example.com/img/mary.jpg',
    message: 'Goodbye'
  },
  {
    id: 3,
    name: 'Chris',
    photo: 'http://example.com/img/chris.jpg',
    message: 'Hey there!'
  },
];

Now we want to map this array to a ListView, each Person object will use an ItemTemplate to produce a ListItem. First, define an ItemTemplate

var personTemplate = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      }
    },
    {
      type: 'Ti.UI.Label',
      bindId: 'name',
      properties: {
        left: 60,
        top: 5,
        height: 40,
        font: { fontSize: '18dp' },
        color: '#fff',
        wordWrap: false,
        ellipsize: true
      }
    },
    {
      type: 'Ti.UI.Label',
      bindId: 'message',
      properties: {
        left: 60,
        top: 45,
        font: { fontSize: '14dp' },
        color: '#eee'
      }
    }
  ]
};

A few things to note here. ItemTemplate defines views inside a ListItem, different from usual way we use add() method to add a view to another view. To compare how to define it with TableViewRow

var person = dataModels[i];
var row = Ti.UI.createTableViewRow({
  // to determine which row is being clicked
  itemId: person.id
});
row.add(Ti.UI.createImageView({
  image: person.photo,
  left: 5,
  top: 5,
  width: 50,
  height: Ti.UI.SIZE
}));
row.add(Ti.UI.createLabel({
  text: person.name,
  left: 60,
  top: 5,
  height: 40,
  font: { fontSize: '18dp' },
  color: '#fff',
  wordWrap: false,
  ellipsize: true
}));
row.add(Ti.UI.createLabel({
  text: person.message,
  left: 60,
  top: 45,
  font: { fontSize: '14dp' },
  color: '#eee'
}));

In childTemplates, is list of UI elements contain inside a ListItem. The way you define the element is by type & properties. bindId is the Person property (data model), which you want the UI to bind to – getting the data out and put it into the view.

Note that in Titanium documentation, there are ItemTemplate and ViewTemplate. ItemTemplate is used to generate a ListItem, while ViewTemplate is to generate other views, which specifically defined by the type property. See how to generate views in custom module, other than those defined in Ti.UI.* namespace.

However we cannot use dataModels array above directly onto ListView, we need to restructure it so it can be compatible with the UI element it is bound to. This process is called binding, which produce an array of view model (of type ListDataItem) that will be assigned to ListViewSection items property:

var viewModels = [
  {
    properties: { itemId: 1 },
    name: { text: 'John' },
    photo: { image: 'http://example.com/img/john.jpg' },
    message: { text: 'Thank you' }
  },
  {
    properties: { itemId: 2 },
    name: { text: 'Mary' },
    photo: { image: 'http://example.com/img/mary.jpg' },
    message: { text: 'Goodbye' }
  },
  {
    properties: { itemId: 3 },
    name: { text: 'Chris' },
    photo: { image: 'http://example.com/img/chris.jpg' },
    message: { text: 'Hey there!' }
  },
];

Notice that each Person properties have been changed, and by now it’s not considered data model any, but rather a view model. text and image are properties of Ti.UI.Label and Ti.UI.ImageView respectively, which you can use to bind the value to UI in the template you define earlier.

A ListView need to explicitly declare at least one ListSection, which acts as a place where you can set & append more ListItem.

var listView = Ti.UI.createListView({
  templates: {
    personTemplate: personTemplate
  },
  defaultItemTemplate: 'personTemplate'
});
var sections = [];
var section = Ti.UI.createListSection();
sections.push(section);
listView.setSections(sections);
win.add(listView);

You can add more template if you have more layout being used in a single ListView. Now load the dataSet into the ListView.

section.setItems(viewModels);

Load more items?

section.appendItems(viewModels);

Deeper visual tree of ItemTemplate

Use childTemplates on each UI element inside the template to customize it even more.

var personTemplate = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      }
    },
    {
      type: 'Ti.UI.View',
      properties: {
        layout: 'vertical',
        left: 60,
        top: 5
      },
      childTemplates: [
        {
          type: 'Ti.UI.Label',
          bindId: 'name',
          properties: {
            left: 0,
            top: 0,
            height: 40,
            font: { fontSize: '18dp' },
            color: '#fff',
            wordWrap: false,
            ellipsize: true
          }
        },
        {
          type: 'Ti.UI.Label',
          bindId: 'message',
          properties: {
            left: 0,
            top: 5,
            font: { fontSize: '14dp' },
            color: '#eee'
          }
        }
      ]
    }
  ]
};

Referring to template above, now you have this visual tree:

ListItem
 |__ ImageView
 |__ View
      |__ Label
      |__ Label

Events

For example, to add click event on image, add events property, specify an object with event name as key & callback as value

var template = {
  childTemplates: [
    {
      type: 'Ti.UI.ImageView',
      bindId: 'photo',
      properties: {
        left: 5,
        top: 5,
        width: 50,
        height: Ti.UI.SIZE
      },
      events: {
        click: function(e) {
          Ti.API.info('photo image clicked!');
        }
      }
    },{
      ...
    }
  ]
};

You can also use itemclick event of ListView to detect if a ListItem was clicked

listView.addEventListener('itemclick', function(e) {
  Ti.API.info('a ListItem was clicked!');

  // get which row is being clicked. See itemId property in viewModels above
  var person = dataModels.reduce(function(p, v) {
    if (v.id == e.itemId) {
      return v;
    }
    return p;
  });
});

Update iOS Titanium module SDK version

If you have a custom module compiled in older SDK version (3.0.0.GA and below), here’s how to update to latest version (3.1.0.GA)

  1. Edit manifest file
    minsdk: 3.1.0.GA
  2. Edit titanium.xcconfig
    TITANIUM_SDK_VERSION = 3.1.0.GA
    TITANIUM_SDK = /Library/Application Support/ ...
    (SDK path point to directory starting from root filesystem, rather than home directory)
Older posts

Copyright © 2017 Noodlecode