{ "id": "173064", "key": "TIMOB-26862", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "20412", "name": "Release 8.1.0", "archived": false, "released": true, "releaseDate": "2019-08-13" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2019-07-15T19:14:24.000+0000", "created": "2019-02-26T15:07:28.000+0000", "priority": { "name": "Medium", "id": "3" }, "labels": [ "TableView", "TextArea", "TextField", "android", "en", "engSchedule" ], "versions": [], "issuelinks": [ { "id": "57274", "type": { "id": "10002", "name": "Duplicate", "inward": "is duplicated by", "outward": "duplicates" }, "inwardIssue": { "id": "172337", "key": "TIMOB-26616", "fields": { "summary": "Android: Infinite focus/blur loop for TextField within TableView on Huawei Honor 10 device", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "Critical", "id": "1" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2019-07-15T19:14:24.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" } ], "description": "*Summary:*\r\nEntering text into a {{TextField}}/{{TextArea}} embedded within a {{TableView}} can cause performance issues if the virtual keyboard shows/hides its top suggestion bar dynamically. It can also cause the cursor to suddenly move to the end of field and cause the field to flicker.\r\n\r\n*Reason:*\r\nBy default, the \"windowSoftInputMode\" is set to {{Ti.UI.Android.SOFT_INPUT_ADJUST_PAN}} (aka: \"adjustPan\"), which means that showing/hiding the virtual keyboards will cause the window to resize. The resizing of a {{TableView}} triggers its row recycling/update behavior which can cause the \"contents\" of each row (such as the {{TextField}}) to be re-parented to a new row container view, which causes havoc with the virtual keyboard.\r\n\r\nFor virtual keyboards that have a fixed height and do not pop-in/out a suggestion bar as you type, this isn't really an issue since the above mentioned resize behavior happens once. For example, this is not an issue on a Pixel phone.\r\n\r\nBut some device pop-in/out a suggestion bar as you type which causes the {{TableView}} to resize every time you type in a character. This will cause massive performance issues.\r\n\r\n*Steps to reproduce:*\r\n# Build and run [^TableOfTextFieldsTest.js] on Android.\r\n# Tap on row 1's {{TextArea}}.\r\n# Notice that the cursor is moved to end of the field. (The code is supposed to highlight the whole text instead.)\r\n# Notice the below warnings get logged.\r\n\r\n{code}\r\n[WARN] IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection\r\n[WARN] IInputConnectionWrapper: getSelectedText on inactive InputConnection\r\n[WARN] IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection\r\n{code}\r\n\r\n*Work-Around 1:*\r\nSet up the window to \"adjustPan\". This works-around the issue because it causes the virtual keyboard to be overlaid on top of the window instead of resizing it, which avoids the issue.\r\n{code:javascript}\r\nvar window = Ti.UI.createWindow({\r\n\twindowSoftInputMode: Ti.UI.Android ? Ti.UI.Android.SOFT_INPUT_ADJUST_PAN : null,\r\n});\r\n{code}\r\n\r\n*Work-Around 2:*\r\nUse a {{ScrollView}} instead of a {{TableView}}.\r\n", "attachment": [ { "id": "66229", "filename": "TableOfTextFieldsTest.js", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-09T00:24:43.000+0000", "size": 1117, "mimeType": "application/x-javascript" }, { "id": "66227", "filename": "TableViewCustomRowTest.js", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-09T00:23:19.000+0000", "size": 1554, "mimeType": "application/x-javascript" }, { "id": "66228", "filename": "TableViewDefaultRowTest.js", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-09T00:23:19.000+0000", "size": 1326, "mimeType": "application/x-javascript" } ], "flagged": false, "summary": "Android: TextField/TextArea within a TableView can have performance issues with some keyboards", "creator": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "subtasks": [], "reporter": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "environment": null, "closedSprints": [ { "id": 1153, "state": "closed", "name": "2019 Sprint 15", "startDate": "2019-07-15T18:30:38.331Z", "endDate": "2019-07-28T18:30:00.000Z", "completeDate": "2019-07-29T15:34:51.860Z", "originBoardId": 114 }, { "id": 1123, "state": "closed", "name": "2019 Sprint 7", "startDate": "2019-03-18T17:27:25.993Z", "endDate": "2019-03-30T17:27:00.000Z", "completeDate": "2019-03-29T18:02:37.441Z", "originBoardId": 114 }, { "id": 1124, "state": "closed", "name": "2019 Sprint 8", "startDate": "2019-03-31T18:03:00.000Z", "endDate": "2019-04-12T18:03:00.000Z", "completeDate": "2019-04-12T19:04:50.175Z", "originBoardId": 114 }, { "id": 1141, "state": "closed", "name": "2019 Sprint 12", "startDate": "2019-06-03T22:00:37.435Z", "endDate": "2019-06-14T22:00:00.000Z", "completeDate": "2019-06-15T03:44:20.826Z", "originBoardId": 114 }, { "id": 1128, "state": "closed", "name": "2019 Sprint 9", "startDate": "2019-04-14T19:05:00.000Z", "endDate": "2019-04-26T19:05:00.000Z", "completeDate": "2019-04-26T22:05:13.933Z", "originBoardId": 114 }, { "id": 1129, "state": "closed", "name": "2019 Sprint 10", "startDate": "2019-04-28T22:06:00.000Z", "endDate": "2019-05-17T22:06:00.000Z", "completeDate": "2019-05-20T16:48:42.522Z", "originBoardId": 114 }, { "id": 1146, "state": "closed", "name": "2019 Sprint 13", "startDate": "2019-06-18T06:36:49.723Z", "endDate": "2019-07-01T06:36:00.000Z", "completeDate": "2019-07-01T16:45:24.126Z", "originBoardId": 114 }, { "id": 1149, "state": "closed", "name": "2019 Sprint 14", "startDate": "2019-07-01T16:55:04.813Z", "endDate": "2019-07-14T16:55:00.000Z", "completeDate": "2019-07-15T15:41:02.311Z", "originBoardId": 114 }, { "id": 1118, "state": "closed", "name": "2019 Sprint 5", "startDate": "2019-02-24T18:36:06.435Z", "endDate": "2019-03-08T18:36:00.000Z", "completeDate": "2019-03-07T22:19:47.057Z", "originBoardId": 114 }, { "id": 1119, "state": "closed", "name": "2019 Sprint 6", "startDate": "2019-03-03T22:20:00.193Z", "endDate": "2019-03-15T22:20:00.000Z", "completeDate": "2019-03-18T17:26:43.120Z", "originBoardId": 114 }, { "id": 1136, "state": "closed", "name": "2019 Sprint 11", "startDate": "2019-05-18T17:39:52.830Z", "endDate": "2019-05-31T17:39:00.000Z", "completeDate": "2019-06-04T21:37:11.485Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "446325", "author": { "name": "spulipakkam", "key": "spulipakkam", "displayName": "Srinivasan Pulipakkam", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~amukherjee] [~jquick] : can you check this.", "updateAuthor": { "name": "spulipakkam", "key": "spulipakkam", "displayName": "Srinivasan Pulipakkam", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-02-26T18:19:52.000+0000", "updated": "2019-02-26T18:19:52.000+0000" }, { "id": "446529", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Calling the Java {{EditText.setText()}} method will cause these warnings because it will replace the internal {{Editable}} Java object. It is better to call the {{EditText.getText().replace()}} method instead, which re-uses the existing {{Editable}} object just like how the keyboard does it. This gets rid of the logged warnings and should be an optimization.\r\n\r\nI've also noticed that these warnings will be logged when showing/dismissing the virtual keyboard with a {{TextField}} and {{TextArea}} *+embedded+* within a Titanium {{TableView}} or {{ListView}}. The issue [~hknoechel] is seeing might be related to [TIMOB-18903]. On an Android phone (not a tablet), these warnings only happen when in portrait orientation too, not landscape since a fullscreen edit screen is displayed. (Embedding them within a {{ScrollView}} works fine; no warnings.)\r\n", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-05T01:27:18.000+0000", "updated": "2019-03-05T02:59:14.000+0000" }, { "id": "446569", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Regarding the warnings caused by showing/dismissing a {{TextField}}'s virtual keyboard while it is embedded within a {{TableView}}, this is only an issue when the \"windowSoftInputMode\" is set to {{SOFT_INPUT_ADJUST_SIZE}}, which is the Android OS' default setting (ie: \"adjustSize\" in the \"AndroidManifest.xml\"). This issue won't happen if the window is set up to use \"adjustPan\" as shown below.\r\n{code:javascript}\r\nvar window = Ti.UI.createWindow({\r\n\twindowSoftInputMode: Ti.UI.Android.SOFT_INPUT_ADJUST_PAN,\r\n});\r\n{code}\r\n\r\nTitanium's {{Ti.UI.TableView}} and {{Ti.UI.ListView}} currently use Google's Java {{ListView}} class internally. From looking at Google's code, its {{onLayout()}} method will flag all of its children/rows as dirty and forcefully reload them. This is triggering the row view recycling behavior and I believe the text field is being quickly detached and re-attached to its row container view. Since \"adjustSize\" is the default setting on Android, the virtual keyboard will force a resize of the app's window and {{ListView}}, which will in turn call its {{onLayout()}} method. I believe this is what's causing the issue. This would also explain why switching to \"adjustPan\" (as shown above) works-around the issue.\r\n[AbsListView.onLayout()|https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/AbsListView.java#L2188]\r\n\r\n[~hknoechel] has shown me a video of his issue. The reason why he's seeing a performance issue is because his device's virtual keyboard pops-up a suggestion bar when typing in the field, which increases the keyboard height. And since \"adjustSize\" is the default, this causing the {{ListView}} to shrink in size and trigger another relayout. The relayout resets the text field's cursor state to the end of the field, which removes the suggestion bar above the keyboard, causing the keyboard height to shrink... which again resizes the app window and {{ListView}} triggering another relayout. This explains the performance issues.\r\n\r\nI'm not able to reproduce the performance issue on a Google Pixel device's virtual keyboard since its top suggestion bar never appears/disappears, meaning the keyboard height doesn't fluctuate. I only see a the logged warnings once when showing the keyboard and again when dismissing the keyboard. I don't see the warnings while typing as [~hknoechel] is seeing since his keyboard suggestion bar is added/removed dynamically.\r\n\r\nI'm not sure how to solve this issue at the moment.\r\n\r\nBut we do know how to work-around it by doing one of the following:\r\n* Use a {{ScrollView}} instead of a {{TableView}}/{{ListView}} when showing text fields\r\n* Set {{TI.UI.Window}} \"windowSoftInputMode\" to {{Ti.UI.Android.SOFT_INPUT_ADJUST_PAN}}.\r\n", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-06T03:36:00.000+0000", "updated": "2019-03-06T03:54:16.000+0000" }, { "id": "446570", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I've noticed that this {{TableView}} issue happens on Android 7.0 and above.\r\n\r\nWhen the native Java {{ListView.onLayout()}} method is called, it triggers the recycling behavior. This calls Titanium's {{Adapter.getView()}} method to see if the given row view container can be re-used or if a new row view container needs to be created. Our current adapter code will always create a new row container for Android 7.0 (aka: API Level 24) and above as can be seen in the link below.\r\n[TiTableView.java#L218|https://github.com/appcelerator/titanium_mobile/blob/master/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tableview/TiTableView.java#L218]\r\n\r\nQuick code snippet...\r\n{code:java}\r\n// TIMOB-24560: prevent duplicate TableViewRowProxyItem on Android N\r\nif (Build.VERSION.SDK_INT > 23) {\r\n\tArrayList models = viewModel.getViewModel();\r\n\tif (models != null && v instanceof TiTableViewRowProxyItem && models.contains(v.getRowData())) {\r\n\t\tv = null;\r\n\t\tsameView = true;\r\n\t}\r\n}\r\n{code}\r\n\r\n\r\nIf I comment out that code, the logged warnings go away... but only if the row was already above the virtual keyboard. The reason this works is that the row container is being re-used and the text field is not being detached and re-attached to another row container. Note that I say it only solves this issue if the row is above the virtual keyboard. If the keyboard overlaps the row, then the old row container is offscreen, which causes it to be recycled, and then a new row container needs to be set up anyways... which is the correct behavior in this case and there is no getting around the logged warnings.\r\n\r\nNow we can't simply comment out that code since it's needed for a reason. Android 7.0 is much more aggressive about recycling rows as can be seen in ticket [TIMOB-24560].\r\n", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-06T04:23:26.000+0000", "updated": "2019-03-06T04:23:26.000+0000" }, { "id": "446653", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "PR (master): https://github.com/appcelerator/titanium_mobile/pull/10745", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-03-09T01:11:17.000+0000", "updated": "2019-03-09T01:11:17.000+0000" }, { "id": "448838", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "PR (8.1.x): https://github.com/appcelerator/titanium_mobile/pull/10936", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-06-05T01:20:20.000+0000", "updated": "2019-06-05T01:20:20.000+0000" }, { "id": "449782", "author": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "body": "FR's Passed.\r\nWaiting for jenkins to merge.", "updateAuthor": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-07-12T23:15:13.000+0000", "updated": "2019-07-12T23:15:13.000+0000" }, { "id": "449809", "author": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "body": "merged to 8_1_X (had already been merged to master)", "updateAuthor": { "name": "cwilliams", "key": "cwilliams", "displayName": "Christopher Williams", "active": true, "timeZone": "America/New_York" }, "created": "2019-07-15T17:01:48.000+0000", "updated": "2019-07-15T17:01:48.000+0000" }, { "id": "449819", "author": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Verified the fix in sdk 8.1.0.v20190715100642 & 8.2.0.v20190715083607.\r\n\r\nClosing.", "updateAuthor": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-07-15T19:14:19.000+0000", "updated": "2019-07-15T19:14:19.000+0000" } ], "maxResults": 10, "total": 10, "startAt": 0 } } }