{ "id": "137586", "key": "TIMOB-17818", "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": "16586", "description": "Release 3.4.2", "name": "Release 3.4.2", "archived": false, "released": true, "releaseDate": "2017-03-11" }, { "id": "16704", "description": "Release 3.5.0", "name": "Release 3.5.0", "archived": false, "released": true, "releaseDate": "2015-01-13" }, { "id": "16593", "description": "Release 4.0.0", "name": "Release 4.0.0", "archived": false, "released": true, "releaseDate": "2015-05-21" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2014-10-23T20:59:43.000+0000", "created": "2014-10-02T07:13:53.000+0000", "priority": { "name": "Medium", "id": "3" }, "labels": [ "android", "exitonclose", "module_window", "qe-manualtest", "qe-testadded" ], "versions": [], "issuelinks": [], "assignee": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2014-11-27T05:32:23.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": "The doc states: \"Boolean value indicating if the application should exit when the Android Back button is pressed while the window is being shown.\"\r\nHowever, exitOnClose closes the app even when the window is closed programatically. The app should be closed only when back is pressed on the window, as written in the doc. There is little sense in closing an app programmatically.\r\n\r\nHere is a problematic (but fairly typical) scenario:\r\n1. First time user entry opens login window\r\n2. After login app opens win1\r\n3. Normally, pressing \"back\" on win 1 should close the app (win1.exitOnClose = true)\r\n4. If logged in at entry, the login page is not opened, and the app opens win1. Again, win1.exitOnClose = true usually does the job\r\n5. However, if the user elects to \"logout\" from the app (e.g. logout menu item on win1), then win1 should close (win1.close() called) and the login window should open. But if win1 closes and exitOnClose == true, then the logout closes the app completely.\r\n\r\nThus, exitOnClose should function for the back button only. Alternatively, exitOnClose can be a dynamic property: win1 in this case will normally have exitOnClose == true, but in case of a logout event win1.exitOnClose should be set to false.", "attachment": [], "flagged": false, "summary": "Android: window.exitOnClose closes app on close(), not just back button", "creator": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "subtasks": [], "reporter": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "environment": "SDK 3.4.0/3.5.0", "closedSprints": [ { "id": 230, "state": "closed", "name": "2014 Sprint 21 SDK", "startDate": "2014-10-13T22:00:57.270Z", "endDate": "2014-10-25T00:00:00.000Z", "completeDate": "2014-10-27T16:33:06.432Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "326721", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "Test app:\r\n{code}\r\nvar win1 = Titanium.UI.createWindow({ \r\n backgroundColor:'#fff'\r\n});\r\n\r\nvar btn1 = Ti.UI.createButton({title: \"Open win\"});\r\nbtn1.addEventListener('click', function(e){\r\n\tvar win2 = Ti.UI.createWindow({exitOnClose: true});\r\n\tvar label1 = Titanium.UI.createLabel({\r\n\t\tcolor:'#999',\r\n\t\ttop: 10,\r\n\t\ttext:'Clicking back should close the app, but pressing the button should not',\r\n\t\tfont:{fontSize:20,fontFamily:'Helvetica Neue'},\r\n\t\ttextAlign:'center',\r\n\t\theight: Ti.UI.SIZE\r\n\t});\r\n\twin2.add(label1);\r\n\tvar btn2 = Ti.UI.createButton({title: \"Close me\"});\r\n\tbtn2.addEventListener('click', function(e){\r\n\t\twin2.close();\r\n\t});\r\n\twin2.add(btn2);\r\n\twin2.open();\r\n});\r\nwin1.add(btn1);\r\nwin1.open();\r\n{code}", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-02T08:31:09.000+0000", "updated": "2014-10-02T08:31:09.000+0000" }, { "id": "326735", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "PR: https://github.com/appcelerator/titanium_mobile/pull/6190\r\ntest on app above. exitOnClose kicks in only for back press and if no androidback event handler defined on the window.\r\nThis matches the docs and is the logical behavior.", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-02T13:45:29.000+0000", "updated": "2014-10-02T13:45:56.000+0000" }, { "id": "328824", "author": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Doc PRs\r\nmaster: https://github.com/appcelerator/titanium_mobile/pull/6243\r\n3_4_X: https://github.com/appcelerator/titanium_mobile/pull/6244\r\n\r\nHi [~mokesmokes] - I have updated the docs to be more explicit. The way to make the app behave the way you are requesting is to have `exitOnClose: false` on the second window, listen for the `androidback` event and close the main window to kill the app when back is pressed. See the example below:\r\n\r\n{code}\r\nvar win1 = Titanium.UI.createWindow({ \r\n backgroundColor:'#fff'\r\n});\r\n \r\nvar btn1 = Ti.UI.createButton({title: \"Open win\"});\r\nbtn1.addEventListener('click', function(e){\r\n var win2 = Ti.UI.createWindow({exitOnClose: false});\r\n var label1 = Titanium.UI.createLabel({\r\n color:'#999',\r\n top: 10,\r\n text:'Clicking back should close the app, but pressing the button should not',\r\n font:{fontSize:20,fontFamily:'Helvetica Neue'},\r\n textAlign:'center',\r\n height: Ti.UI.SIZE\r\n });\r\n win2.add(label1);\r\n var btn2 = Ti.UI.createButton({title: \"Close me\"});\r\n btn2.addEventListener('click', function(e){\r\n win2.close();\r\n });\r\n win2.add(btn2);\r\n win2.open();\r\n\r\n win2.addEventListener('androidback', function() {\r\n win1.close();\r\n });\r\n\r\n});\r\nwin1.add(btn1);\r\nwin1.open();\r\n{code}\r\n\r\nThank you for your help with this issue.", "updateAuthor": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-10-21T15:36:17.000+0000", "updated": "2014-10-21T16:19:02.000+0000" }, { "id": "328826", "author": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Docs updated", "updateAuthor": { "name": "vduggal", "key": "vduggal", "displayName": "Vishal Duggal", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-10-21T15:51:10.000+0000", "updated": "2014-10-21T15:51:10.000+0000" }, { "id": "328832", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "[~jalter] I'm afraid to say that fixing the docs doesn't do it..... The previous documented logic (and my PR) is the correct and more general mechanism. Here is the scenario I have in mind:\r\n1. App initialization checks if user is logged in (e.g. using Facebook module).\r\n2. If user is not logged in, then login window is shown. Clearly this window has exitOnClose = true.\r\n3. User logs in, and app goes to App Window. Per your suggestion this window has exitOnClose = false. If back is clicked, we can close the login window to close the app.\r\nBUT:\r\n4. Next time the user opens the app, he is already logged in, the login window is never opened, and the app immediately goes into the App Window.\r\n5. Let's assume the App Window has a logout button. If the logout button is clicked, App Window should close and the login window opened. In this case, clearly the App Window needs exitOnClose = false;\r\n6. However, if the user doesn't log out (users rarely need to log out, of course), then the back button should close the app (exitOnClose = true).\r\n\r\nSo the bottom line is, we don't always know ahead of time how to set exitOnClose (for the App Window in this case). I'm not sure how this can be fixed with the current SDK.... Here are the solutions I see:\r\n1. exitOnClose only takes effect for the back button - as originally documented and as per my PR.\r\n2. exitOnClose can be set/changed at any time on the Window\r\n2. Get rid of exitOnClose entirely, and create an exitApp() function for Android that can be called whenever the developer chooses.\r\n\r\nDo you have a method to implement the common scenario I described above, with the current API?\r\nThanks!\r\n\r\nEDIT: options 2 and 3 above are ideal, option 1 (my PR) is somewhat of a hack, but it seems to me that logically it is usually what is intended. ", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-21T16:18:15.000+0000", "updated": "2014-10-21T16:42:19.000+0000" }, { "id": "328893", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "This has been marked for 3.4.2 after it was marked \"Won't Fix\". [~ingo], [~jalter] can you please clarify and also please respond to the problematic scenario in my previous comment today? Thanks.", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-21T20:32:09.000+0000", "updated": "2014-10-21T20:32:09.000+0000" }, { "id": "328925", "author": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~mokesmokes] - Here are two alternate ways to handle the windows in the scenario you mentioned without and changes to the TiSDK.\r\n\r\n1. Open the main app window first with `exitOnClose: true` with no content (only add the content after the you are logged in). Open the login window without `exitOnClose` on top of the app window. Listen for the `androidback` event on the login window and close the main window if the event it is fired. This will make hitting the back button on the login or app windows kill the app and you can still close the login window at will.\r\n2. Another option would be to make the login screen be a full size view and add it to the app window. This will make the back button click affect app window and you can remove the login view at will.\r\n\r\nIn both of these cases, you would not show login if you are already logged in.\r\n\r\nIf you do not like either of these, option #2\r\n{code}\r\n2. exitOnClose can be set/changed at any time on the Window\r\n{code}\r\nwould be the best option. When you pass `exitOnClose` to `createWindow` it is just setting a boolean on the intent that is read when the window is being closed. So, it shouldn't be a big deal to change that boolean after the window is open. If you would like to make that change, I will be happy to review the PR.", "updateAuthor": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-10-21T22:31:25.000+0000", "updated": "2014-10-21T22:31:25.000+0000" }, { "id": "328972", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "I will make the change to exitOnClose. Why is exitOnClose set on the intent instead of simply being yet another property on the Window proxy? The options with the current SDK are too twisted and complicate app logic for no good reason. Not only should the SDK be straightforward to use, but consider that this is an Android-only problem, and I shouldn't need to architect a strange workaround for this when the \"straight-forward\" code just works on iOS. Consider the frustration of a developer who codes and tests on iOS first and then tries to port to Android.....\r\n\r\nAdditionally, I still think my current PR is valid: In Android apps are closed when the user navigates away from them (i.e. back is pressed). Thus it makes sense that exitOnClose be tied to the back button. If the developer needs logic at window close he has the close event, no reason to do it at androidback. If he needs to distinguish between a programmatic close and androidback, he can set a variable when closing, etc. So bottom line - my current PR is perfectly valid and far better than loopy workarounds with windows, views, etc.", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-22T04:36:26.000+0000", "updated": "2014-10-22T06:14:38.000+0000" }, { "id": "329173", "author": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "body": "I don't think it should only be tied to the back button. If you Google scenarios for closing an app programmatically, there are a lot and some very valid. I actually like having both #2 & #3 as options for the developer. This adds maximum flexibility for the user.", "updateAuthor": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "created": "2014-10-23T15:26:42.000+0000", "updated": "2014-10-23T15:26:42.000+0000" }, { "id": "329175", "author": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "body": "WHoops. Let me clarify. When I say #2 & #3 - I mean ... I like the idea of having a kill switch for the app (which native Android devs can do) AND having the ability to programmatically set the exitOnClose, on demand. ", "updateAuthor": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "created": "2014-10-23T15:32:12.000+0000", "updated": "2014-10-23T15:32:12.000+0000" }, { "id": "329194", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "PR implementing option #2: https://github.com/appcelerator/titanium_mobile/pull/6263\r\nPersonally I think this is good enough - setting the flag and closing the window is the same as a separate function.\r\nTest app demoing both window and tabgroup close:\r\n{code}\r\nvar win1 = Titanium.UI.createWindow({ \r\n});\r\n\r\nvar btn1 = Ti.UI.createButton({title: \"Open win\", top: '50dp'});\r\nbtn1.addEventListener('click', function(e){\r\n\tvar win2 = Ti.UI.createWindow();\r\n\tvar exitOnClose = Ti.UI.createSwitch({title: 'exitOnClose', top: '50dp', color: 'white', style: Titanium.UI.Android.SWITCH_STYLE_CHECKBOX});\r\n\texitOnClose.addEventListener('change', function(e){\r\n\t\twin2.exitOnClose = e.value;\r\n\t});\r\n\twin2.add(exitOnClose);\r\n\tvar btn2 = Ti.UI.createButton({title: \"Close me\", top: '200dp'});\r\n\tbtn2.addEventListener('click', function(e){\r\n\t\twin2.close();\r\n\t});\r\n\twin2.add(btn2);\r\n\twin2.open();\r\n});\r\nwin1.add(btn1);\r\n\r\nvar btn2 = Ti.UI.createButton({title: \"Open TabGroup\", top: '200dp'});\r\nbtn2.addEventListener('click', function(e){\r\n\tvar tabGroup = Titanium.UI.createTabGroup();\r\n\t\r\n\tvar tabWin1 = Titanium.UI.createWindow({ \r\n\t title:'Tab 1',\r\n\t});\r\n\tvar tab1 = Titanium.UI.createTab({ \r\n\t title:'Tab 1',\r\n\t window:tabWin1\r\n\t});\r\n\t\r\n\tvar exitOnClose = Ti.UI.createSwitch({title: 'exitOnClose', top: '50dp', color: 'white', style: Titanium.UI.Android.SWITCH_STYLE_CHECKBOX});\r\n\texitOnClose.addEventListener('change', function(e){\r\n\t\ttabGroup.exitOnClose = e.value;\r\n\t});\r\n\ttabWin1.add(exitOnClose);\r\n\tvar btn2 = Ti.UI.createButton({title: \"Close me\", top: '200dp'});\r\n\tbtn2.addEventListener('click', function(e){\r\n\t\ttabGroup.close();\r\n\t});\r\n\ttabWin1.add(btn2);\r\n\t\r\n\tvar tabWin2 = Titanium.UI.createWindow({ \r\n\t title:'Tab 2',\r\n\t});\r\n\tvar tab2 = Titanium.UI.createTab({ \r\n\t title:'Tab 2',\r\n\t window:tabWin2\r\n\t});\r\n\t\r\n\ttabGroup.addTab(tab1); \r\n\ttabGroup.addTab(tab2); \r\n\t\r\n\ttabGroup.open();\r\n});\r\nwin1.add(btn2);\r\n\r\nwin1.open();\r\n{code}", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-23T17:08:11.000+0000", "updated": "2014-10-23T17:08:11.000+0000" }, { "id": "329240", "author": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "body": "PRs with CR fix\r\nmaster: https://github.com/appcelerator/titanium_mobile/pull/6265\r\n3_4_X: https://github.com/appcelerator/titanium_mobile/pull/6264\r\n\r\nThank you [~mokesmokes]!", "updateAuthor": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-10-23T20:57:06.000+0000", "updated": "2014-10-23T20:59:33.000+0000" }, { "id": "329241", "author": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "Thanks for the fast review! :)", "updateAuthor": { "name": "mokesmokes", "key": "mokesmokes", "displayName": "Mark Mokryn", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-10-23T21:00:19.000+0000", "updated": "2014-10-23T21:00:19.000+0000" }, { "id": "333995", "author": { "name": "vchidambaram", "key": "vchidambaram", "displayName": "Visalakshi Chidambaram", "active": true, "timeZone": "Asia/Shanghai" }, "body": "Test environment:\r\nAppc-Studio: 3.4.1.201410281743\r\nTitanium SDK: 3.5.0v20141125154115 \r\nTitanium CLI: 3.4.1 GA\r\nAlloy : 1.5.1 GA\r\nOS: Mac OSX 10.9.4\r\nDevice: Samsung Galaxy S5 (4.4.2)", "updateAuthor": { "name": "vchidambaram", "key": "vchidambaram", "displayName": "Visalakshi Chidambaram", "active": true, "timeZone": "Asia/Shanghai" }, "created": "2014-11-27T05:32:23.000+0000", "updated": "2014-11-27T05:32:23.000+0000" } ], "maxResults": 17, "total": 17, "startAt": 0 } } }