Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-5303] Android: UI view size property values incorrect

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionFixed
Resolution Date2012-04-25T08:02:03.000+0000
Affected Version/sRelease 1.7.3, Release 1.8.0.1
Fix Version/sRelease 2.0.0
ComponentsAndroid
Labelscore, qe-port
ReporterIvan Skugor
AssigneeAllen Yeung
Created2011-08-19T02:01:07.000+0000
Updated2012-07-16T16:31:22.000+0000

Description

Problem

The view's size property values are intended to reflect the rendered dimensions. However, if these properties are queried too soon after rendering has completed, the old values will be output. The advice of querying property values within a window open event listener does not work.

Test case

  var win = Ti.UI.createWindow({
// fullscreen: false, // once fixed, please check with this line uncommented
backgroundColor:'red'
});

var view = Ti.UI.createView({
  backgroundColor:'blue',
  width:'50',
  height:50
});

win.add(view);

Ti.API.info("*** Before window open - view.size.width = 50 ***");
Ti.API.info("view.size w x h: " + view.size.width + " x " + view.size.height); // incorrect

view.width = 100;

win.addEventListener('open', function(){
  Ti.API.info("*** Window open - view.size.width = 100 ***");
  Ti.API.info("view.size w x h: " + view.size.width + " x " + view.size.height); // incorrect
});


Ti.Gesture.addEventListener('orientationchange', function(){
  Ti.API.info("*** orientationchange - view.size.width = 200 ***");
  view.width = 200;
  Ti.API.info("view.size w x h: " + view.size.width + " x " + view.size.height); // incorrect
});
win.open();

Logs

  1454         AndroidRuntime  D  >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
  1454         AndroidRuntime  D  CheckJNI is ON
   60     InputManagerService  W  Got RemoteException sending setActive(false) notification to pid 1430 uid 10044
  1454         AndroidRuntime  D  --- registering native functions ---
   60         ActivityManager  I  Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.appcelerator.testing10/.Testing10Activity }
   60         ActivityManager  I  Start proc com.appcelerator.testing10 for activity com.appcelerator.testing10/.Testing10Activity: pid=1461 uid=10044 gids={1015, 3003}
  1454         AndroidRuntime  D  Shutting down VM
  1454         AndroidRuntime  I  NOTE: attach of thread 'Binder Thread #3' failed
  1461          TiApplication  I  (main) [0,0] checkpoint, app created.
  1461          TiApplication  I  (main) [536,536] Titanium 1.8.0.1 (2011/12/22 13:09 fbdc96f)
  1461              TiFastDev  D  (main) [164,700] Enabling Fastdev on port 58902
  1461              TiFastDev  D  (main) [10,710] sent tokens successfully
  1461              TiFastDev  D  (main) [1,711] Fastdev session handshake succesful.
  1461          TiApplication  I  (main) [8,719] Titanium Javascript runtime: v8
  1461          TiApplication  W  (main) [68,787] activity stack is emtpy, unable to get current activity
  1461         TiRootActivity  I  (main) [0,0] checkpoint, on root activity create, savedInstanceState: null
  1461          TiApplication  W  (main) [119,119] activity stack is emtpy, unable to get current activity
  1461          TiApplication  W  (main) [1,120] activity stack is emtpy, unable to get current activity
  1461          TiApplication  W  (main) [1,121] activity stack is emtpy, unable to get current activity
  1461          TiApplication  E  (KrollRuntimeThread) [937,1058] APP PROXY: ti.modules.titanium.app.AppModule@44eb3110
  1461          TiAssetHelper  D  Fetching "app.js" with Fastdev...
  1461                  TiAPI  I  *** Before window open - view.size.width = 50 ***
  1461                  TiAPI  I  view.size w x h: 0 x 0
  1461                  TiAPI  I  *** Window open - view.size.width = 100 ***
  1461                  TiAPI  I  view.size w x h: 0 x 0
  1461         TiRootActivity  I  (main) [0,0] checkpoint, on root activity resume. activity = com.appcelerator.testing10.Testing10Activity@44e76478
   60         ActivityManager  I  Displayed activity com.appcelerator.testing10/.Testing10Activity: 3359 ms (total 3359 ms)
   60           WindowManager  I  Setting rotation to 1, animFlags=1
   60         ActivityManager  I  Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=2/1/1 nav=3/1 orien=2 layout=34 uiMode=17 seq=70}
  1461                  TiAPI  I  *** orientationchange - view.size.width = 200 ***
  1461                  TiAPI  I  view.size w x h: 100 x 50
   60         ActivityManager  I  Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=2/1/2 nav=3/1 orien=2 layout=34 uiMode=17 seq=71}
   60           WindowManager  I  Setting rotation to 0, animFlags=0
   60         ActivityManager  I  Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=2/1/2 nav=3/1 orien=1 layout=34 uiMode=17 seq=72}
  1461                  TiAPI  I  *** orientationchange - view.size.width = 200 ***
  1461                  TiAPI  I  view.size w x h: 200 x 50

Discussions

See Q/A topic: http://developer.appcelerator.com/question/124426/how-does-dimensions-work#216821

Workaround

Using setTimeout allows the size values to be updated to reflect the re-rendered view.

Comments

  1. Paul Dowsett 2011-09-18

    Thanks for raising this ticket, Ivan. I've verified the behavior and so am moving it to the main project now.
  2. Arthur Evans 2011-12-20

    I think what is missing here is an event to tell the application that the view has finished laying out components. That is, when you update the width or height property, you trigger a new layout cycle, but we don't have any way to tell when it finishes.
  3. Ivan Skugor 2011-12-21

    I'm not Android Java programmer, so I can't comment is that possible to do or not. But - I think this can be done from JavaScript. :) Currently I'm using one approach in my app to get this functionality right, but in limited edition that suits my needs. Now, I have made basic example that shows idea how the problem from this ticket can be solved using pure JavaScript. The code can be found here: https://gist.github.com/1505988 It just shows basic idea and it needs more work to make it work in in any case (different layout mechanisms, position properties, something else?). While working on that example, I noticed issue. I'm working on Windows XP, example is compiled with Titanium SDK version: 1.9.0 (12/20/11 19:16 fd3b938) and I'm using Android 3.1 (Lenovo Thinkpad tablet). View can't be re-sized when it has dimension defined in configuration object passed to factory method, or when dimension property is set with non-numeric value (percentage, 'auto'). You can see this issue by running example, pressing "Set fixed" button, "Set percentage" button and then "Set fixed" button (last attempt to set fixed value will fail), or by commenting out a line that defines view's height:
       var view = Ti.UI.createView({
       	backgroundColor: '39c',
       	height: 700,
       	layout: 'vertical'
       });
       
    That is obvious bug, I think. (ah yes, almost forgot, "alert" dialogs don't show in right order) Now, to return to the point, as you can see, dimensions can be calculated from JavaScript. I think that you use some kind of JS wrappers to native components, so if this can't be implemented in Java, maybe it can in JavaScript. :) Hope this helps. Cheers
  4. Paul Dowsett 2012-01-16

  5. Ivan Skugor 2012-01-16

    But what if window is already opened (I can change element size after window opens)? My initial ticket report was about that. We need to write "setTimeout" and other magic just to get basic thing as element size?!
  6. Paul Dowsett 2012-01-17

    I have reopened and updated this ticket, as the values are incorrect even using the the advice to use the window open event.
  7. Ivan Skugor 2012-02-01

  8. Justin Toth 2012-02-14

    It seems that the size dimensions aren't available on window open, but they are a few milliseconds later. This code was run in the Android Emulator using Ti 1.8.1 and Android 2.2:
       //create label.
       var win = Ti.UI.createWindow({ backgroundColor: "#fff" });
       
       //create labels.
       var labelAddedBeforeWinOpen = Ti.UI.createLabel({ text: "Created before win open", 
       	width: "auto", height: "auto", top: 20 });
       var labelAddedAfterWinOpen = Ti.UI.createLabel({ text: "Created after win open", 
       	width: "auto", height: "auto", bottom: 20 });
       	
       //add first label.
       win.add(labelAddedBeforeWinOpen);
       
       win.addEventListener("open", function() { 
       
       	//add second label.
       	win.add(labelAddedAfterWinOpen);
       	
       	//log label dimensions over time.
       	logDimensions(0);
       	logDimensions(1);
       	logDimensions(10);
       	logDimensions(100);
       	logDimensions(1000);
       	
       });
       
       //open window.
       win.open();
       
       function logDimensions(wait) {
       	
       	if(wait > 0) {
       		setTimeout(function() { 
       			Ti.API.info("labelCreatedBeforeWinOpen height after timeout of " + wait + "ms: " + labelAddedBeforeWinOpen.size.height);
       			Ti.API.info("labelCreatedAfterWinOpen height after timeout of " + wait + "ms: " + labelAddedAfterWinOpen.size.height);
       		}, wait);
       	}
       	else {
       		Ti.API.info("labelCreatedBeforeWinOpen height after no timeout: " + labelAddedBeforeWinOpen.size.height);
       		Ti.API.info("labelCreatedAfterWinOpen height after no timeout: " + labelAddedAfterWinOpen.size.height);
       	}
       	
       }
       
    The result is: I/TiAPI ( 603): labelCreatedBeforeWinOpen height after no timeout: 0 I/TiAPI ( 603): labelCreatedAfterWinOpen height after no timeout: 0 I/TiAPI ( 603): labelCreatedBeforeWinOpen height after timeout of 1ms: 0 I/TiAPI ( 603): labelCreatedAfterWinOpen height after timeout of 1ms: 19 I/TiAPI ( 603): labelCreatedBeforeWinOpen height after timeout of 10ms: 19 I/TiAPI ( 603): labelCreatedAfterWinOpen height after timeout of 10ms: 19 I/TiAPI ( 603): labelCreatedBeforeWinOpen height after timeout of 100ms: 19 I/TiAPI ( 603): labelCreatedAfterWinOpen height after timeout of 100ms: 19 I/TiAPI ( 603): labelCreatedBeforeWinOpen height after timeout of 1000ms: 19 I/TiAPI ( 603): labelCreatedAfterWinOpen height after timeout of 1000ms: 19
  9. Justin Toth 2012-02-14

    Here is a possible workaround for this issue, which will check every 10ms if the size has been calc'd for the window object yet. Once it has then it will call the callback function, which the developer can use as their window load function.
       //create window.
       var win = Ti.UI.createWindow({ backgroundColor: "#fff" });
       
       //create label.
       var label = Ti.UI.createLabel({ text: "Added before win open", 
       	width: "auto", height: "auto", top: 20 });
       win.add(label);
       
       //check for when dimensions are available.
       waitForDimensions(win, function() { 
       	//dimensions have been available, use this as window load function.
       	alert("label height: " + label.size.height);
       });
       
       //open window.
       win.open();
       
       function waitForDimensions(window, callback, numTries) {
       	
       	if(!numTries)
       		numTries = 0;
       	if(!window.size || window.size.height == 0) {
       		numTries++;
       		if(numTries < 500) {
       			setTimeout(function() { 
       				waitForDimensions(window, callback, 1);
       			}, 10);
       		}
       		else
       			alert("Waited 5 seconds but never got back size dimensions!");
       	}
       	else {
       		alert("Size dimensions were calc'd after " + numTries * 10 + "ms!");
       		callback();
       	}
       	
       }
       
  10. Ivan Skugor 2012-02-15

    Yeah, I also noticed that "open" event is (sometimes) triggered before window is actually rendered. For example, I show activity indicator before creating a window and it's content and I use window's "open" event to hide activity indicator. In some cases, activity indicator hides before window content is visible on screen. Introducing timeout for activity indicator hiding (about 100ms) solves that issue. But I think that issue in different thing that has influence on this issue.
  11. Julian Krispel-Samsel 2012-02-16

    Getting the height of a view with the setTimeout method has worked for me. Until I have more items than fit on the screen. It seems to be impossible to get the height of a view that is not on screen, even though one can for example scroll to reveal it.
  12. Paul Dowsett 2012-02-16

    Julian Please raise a new ticket for that issue, including a simple test case. Read [How to Submit a Bug Report](https://wiki.appcelerator.org/display/guides/How+to+Submit+a+Bug+Report) to find out the correct way to do this. Many thanks for your input.
  13. Allen Yeung 2012-04-24

    In 2.0, there is a new 'postlayout' event which is fired after a layout pass occurs. If you replace the 'open' event with 'postlayout' it should work as expected. Please refer to http://docs.appcelerator.com/titanium/2.0/index.html#!/guide/Transitioning_to_the_New_UI_Layout_System for more information.
  14. Ivan Skugor 2012-04-25

    OK, thanks. But, IMHO, this ticket shouldn't be marked as invalid (since it's valid ticket - at least it was at the time it was reported).
  15. Neeraj Gupta 2012-04-25

    Agreed. This ticket was fixed as part of composite layout implementation in 2.0 release.
  16. Ivan Skugor 2012-04-25

    Thanks Neeraj. :)
  17. Rima Umbrasas 2012-07-16

    Verified fixed on: Titanium SDK: 2.2.0.v20120716092112 Titanium Studio, build: 2.1.1.201207121732 Devices Android 3.2 and Android emulator.

JSON Source