Noodlecode parody of spaghetti code

Tag: android (page 2 of 2)

Method calling thread for Titanium module (Android)

When creating method for a Titanium module in Android, using @Kroll.method annotation, this method will be invoked on KrollRuntimeThread. This is important to know if that method is accessing UI component, such as TextView or WebView, since all UI related components must be handled on main thread.

@Kroll.method annotation have one convenient argument that allow method to be called on UI thread

@Kroll.method(runOnUiThread=true)
public void show() {}

However, it is deprecated – as noted on Specific Changes no. 18 of Android Module Porting Guide , you can use TiMessenger class.

Or, easier way is to use Activity runOnUiThread method

@Kroll.method
public void show() {
    getActivity().runOnUIThread(new Runnable() {
        @Override
        public void run() {
            // code here
        }
    });
}

Add project as library – Titanium module (Android)

Native Android application can include other projects as library, when they need that particular SDK library functionalities – using Eclipse, right click project > Properties > Android > Library. However for Titanium module, there’s no option to add other projects as library. Instead, we need to include the whole project itself into the module project directory.

For example, I’m creating GooglePlayServices module, so I need to copy files from ANDROID_SDK_DIR/extras/google/google_play_services/libproject/google-play-services_lib/ into the module folder

export MODULE_DIR=~/Workspace/ti.googleplayservices/
cd $ANDROID_SDK_DIR
cp -r libs/*.jar $MODULE_DIR/lib
cp -r res/ $MODULE_DIR/platform/android/
cp -r src/ $MODULE_DIR/src/

Most of the time, the library that being included will be missing R.java and BuildConfig.java file. Depending on the library itself, create those files manually. For this GooglePlayServices module, the generated resource file (R.java) is located at com.google.android.gms namespace:

touch $MODULE_DIR/src/com/google/android/gms/R.java

(for Facebook SDK that I created sometime ago, it’s at com.facebook.android)

Now, again depending on the library we’re including, the content of R.java can be found either from the source code (like Facebook SDK) or from the documentation (GooglePlayServices).

Originally, R.java is generated by Android build tool when building APK file for a project, but since we’re building a Titanium module, R.java cannot be generated, since it’s value might clash with the actual project that use this module. Values inside R.java are generated from the XML resources (such as strings.xml, values.xml, layout.xml), or graphic files (icons / images files, also known as drawables).

The difference between this file and the generated one, is that we need to initialize it to populate its values. By using TiRHelper, we will get and assign all the values at runtime.

package com.google.android.gms;

import org.appcelerator.titanium.util.TiRHelper;
import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException;

public class R {

    public static class attr {
        public static int adSize;
        public static int adUnitId;
        public static int allowShortcuts;
        // ... more ...
    }

    public static class color {
        // ...
    }

    public static class drawable {
        // ...
    }

    public static class id {
        // ...
    }

    public static class integer {
        // ...
    }

    public static class string {
        // ...
    }

    public static class styleable {
        // ...
        public static final int[] AdsAttrs = {
            styleable.AdsAttrs_adSize,
            styleable.AdsAttrs_adSizes,
            styleable.AdsAttrs_adUnitId,
        };
        // ... more ...
    }

    public static void initialize() {
        R.attr.adSize = getResource("attr.adSize");
        R.attr.adUnitId = getResource("attr.adUnitId");
        R.attr.allowShortcuts = getResource("attr.allowShortcuts");
        // ... more ...
    }

    private static int getResource(String path) {
        int id = 0;
        try {
            id = TiRHelper.getResource(path);
        } catch (ResourceNotFoundException e) {}
        return id;
    }
}

Then, in the main module class, don’t forget to call initialize() method on app creation.

@Kroll.onAppCreate
public static void onAppCreate(TiApplication app) {
    R.initialize();
}

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"

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;
  });
});

Google Play Store updated UI for Gingerbread

playstore-2013-05-06-102022

playstore-2013-05-06-101958

playstore-2013-05-06-101936

New version of Play Store implements UI based on latest Android version on Gingerbread (Android 2.3). Overall, the design emphasis on cleaner look, larger thumbnails, and updated UI elements such as action icons & flat color. App detail page still maintain existing layout, with larger thumbnail & more paragraph spacing.

Setup Titanium Studio environment for developing Titanium module (Android) on Windows

Using Titanium Studio 2.1.2.201208301612 and Titanium Mobile 2.1.4.GA

  1. Install Android SDK on path without space (C:\Android\android-sdk)
  2. Install SDK platform AND Google API – 2.2 to latest (4.2)
  3. Install Android NDK (C:\Android\android-ndk-r8b)
  4. Add titanium.py to PATH
    C:\Users\Username\AppData\Roaming\Titanium\mobilesdk\win32\2.1.4.GA
  5. Add python to PATH (if you don’t have python installed):
    C:\Users\Username\AppData\Local\Titanium Studio\plugins\com.appcelerator.titanium.python.win32_1.0.0.1338515509\python

    (Browse into the folder if want to know exact foldername)

  6. Install Gperf and add to PATH
    C:\Program Files\Gnu\Win32\bin

Create new module

  1. Open Titanium Studio
  2. Right click Project Explorer > New > Titanium Mobile Module Project
  3. You should be able to select Android as platform
  4. Continue until finish creating
  5. Edit build.properties, add
    android.ndk=C:\Android\android-ndk-r8b

Build & package module

  1. Right click build.xml > Run As > Ant Build
  2. Packaged module zip file is in dist folder

Designing web page for Android webview

Android screen built-in resolutions (based on API 8-16):

Skin Resolution (w x h pixel) Orientation
QVGA 240 x 320 Portrait
WQVGA400 240 x 400 Portrait
WQVGA432 240 x 432 Portrait
HVGA 320 x 480 Portrait
WVGA800 480 x 800 Portrait
WVGA854 480 x 854 Portrait
WSVGA 1024 x 600 Landscape
WXGA720 1280 x 720 Landscape
WXGA, WXGA800, WXGA800-7in 1280 x 800 Landscape

Android screen density

Density DPI / Pixel ratio Skin
ldpi 120 / 0.75 QVGA, WQVGA400, WQVGA432
mdpi 160 / 1.0 HVGA, WSVGA, WXGA
tvdpi 213 / 1.33125 WVGA800-7in
hdpi 240 / 1.5 WVGA800, WVGA854
xhdpi 320 / 2.0 WXGA720, WXGA800

Android webview viewport size (100% width and height)

Skin Viewport (w x h pixel, portrait)
QVGA 320 x 427
WQVGA400 320 x 533
WQVGA432 320 x 576
HVGA 320 x 480
WVGA800 320 x 533
WVGA854 320 x 569
WSVGA 600 x 1024
WXGA720 360 x 598
WXGA 800 x 1280
WXGA800 400 x 598
WXGA800-7in 601 x 962

In the HTML page, make sure we lock the webpage from being resize/zoom

<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />

To design responsive web page, pay attention to webview viewport size in writing the css media query. From the viewport size table above, if we’re designing for portrait orientation only, we have a set of widths: 320, 360, 400, 600, 800 (601 can use 600 width styles). So our css code must be organized like this:

/* width: 800 */
.container { width: 800px; }

/* width: 600 */
@media (max-width: 799px) {
    .container { width: 600px; }
}

/* width: 400 */
@media (max-width: 599px) {
    .container { width: 400px; }
}

/* width: 360 */
@media (max-width: 399px) {
    .container { width: 360px; }
}

/* width: 320 */
@media (max-width: 359px) {
    .container { width: 320px; }
}

Note: Since landscape width got more, choose widths that can be grouped together, e.g: 420, 480, 530, 960, 1024, 1280

In handling different screen density using css, it’s recommended to display images as background of html elements.
HTML:

<i class="icon ok"></i>

CSS:

.icon {
    width: 50px;
    height: 50px;
    display: block;
    background-size: 50px 50px;
}
.icon.ok {
    background-image: url(img/mdpi/icon_ok.png);
}

That’s for the base density (mdpi). For other densities:

/* density: xhdpi */
@media screen and (-webkit-device-pixel-ratio: 2.0) {
    .icon.ok { background-image: url(img/xhdpi/icon_ok.png); }
}

/* density: hdpi */
@media screen and (-webkit-device-pixel-ratio: 1.5) {
    .icon.ok { background-image: url(img/hdpi/icon_ok.png); }
}

/* density: ldpi */
@media screen and (-webkit-device-pixel-ratio: 0.75) {
    .icon.ok { background-image: url(img/ldpi/icon_ok.png); }
}

Note: for tvdpi, it’ll use hdpi stylesheet & resize accordingly.

Image sizes. To know what image size you need, use pixel ratio to resize the image (base design is using 1.0 pixel ratio). Image DPI (dot per inch) is 72. E.g:

Image file Density / Pixel ratio Size
img/ldpi/icon_ok.png ldpi / 0.75 50 x 0.75 = 38px
img/mdpi/icon_ok.png mdpi / 1.0 50 x 1.0 = 50px
img/hdpi/icon_ok.png hdpi / 1.5 50 x 1.5 = 75px
img/xhdpi/icon_ok.png xhdpi / 2.0 50 x 2.0 = 100px
Newer posts

Copyright © 2017 Noodlecode