Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-26758] Android: Changing TabbedBar properties "labels" and "index" before opening window causes a crash

GitHub Issuen/a
TypeBug
PriorityMedium
StatusClosed
ResolutionFixed
Resolution Date2019-04-03T09:26:38.000+0000
Affected Version/sRelease 8.0.0
Fix Version/sRelease 8.0.1
ComponentsAndroid
Labelsandroid, engSchedule, parity, tabbedbar, tabs
ReporterHans Knöchel
AssigneeJoshua Quick
Created2019-01-21T12:03:59.000+0000
Updated2019-04-03T09:26:38.000+0000

Description

*Summary:* Changing Ti.UI.TabbedBar properties "labels" or "index" after creating the tabbed bar but before its parent window will cause a crash. This is a parity issue. iOS allows these properties to be changed dynamically. *Steps to reproduce:*

Build and run the below code on Android.

On app startup, notice it crashes.

var window = Ti.UI.createWindow();
var tabbedBar = Ti.UI.createTabbedBar();
tabbedBar.labels = ["One", "Two", "Three"];
tabbedBar.index = 1;
Ti.API.info("@@@ TabbedBar.index: " + tabbedBar.index);
window.add(tabbedBar);
window.open();
*Result:* Will get the below error when setting the "labels" property.
[ERROR] TiExceptionHandler: Error: Attempt to invoke virtual method 'void ti.modules.titanium.ui.widget.TiUITabbedBar.setNewLabels()' on a null object reference
Will get the below error when setting the "index" property.
[ERROR] TiExceptionHandler: Error: Attempt to invoke virtual method 'void ti.modules.titanium.ui.widget.TiUITabbedBar.setSelectedIndex(int)' on a null object reference
*Work-Around:* Move all property TabbedBar property changing code to the window's open event handler. Or avoid setting these property dynamically and assign them once via its creation properties.
var window = Ti.UI.createWindow();
var tabbedBar = Ti.UI.createTabbedBar({
	labels: ["One", "Two", "Three"],
	index: 1,
});
window.add(tabbedBar);
window.open();

Attachments

FileDateSize
20190320_113615_2.mp42019-03-20T10:44:57.000+00004597403

Comments

  1. Joshua Quick 2019-01-23

    I can reproduce this issue with the below code.
       var window = Ti.UI.createWindow();
       var tabbedBar = Ti.UI.createTabbedBar();
       tabbedBar.labels = ["One", "Two", "Three"];
       window.add(tabbedBar);
       window.open();
       
    Thanks for bringing this issue to our attention. We'll look into it.
  2. Joshua Quick 2019-01-24

    PR (master): https://github.com/appcelerator/titanium_mobile/pull/10646
  3. Hans Knöchel 2019-01-28

    Is there a chance to get this customer-prio bumped into 8.0.0? We need this for our production app and want to prevent curating a custom SDK.
  4. Joshua Quick 2019-01-29

    PR (8.0.x): https://github.com/appcelerator/titanium_mobile/pull/10655
  5. Joshua Quick 2019-01-29

    [~hknoechel], out of curiosity, is there a reason why you can't set the labels/index via the TabbedBar creation properties? That would work-around the problem.
  6. Hans Knöchel 2019-01-29

    We have quite complex use cases where the index is adjusted based on user defaults (e.g. private vs shared expenses). Other use cases require the tabbed bar to change on runtime, e.g to change it's selection based on a certain state change. The only workaround for that would be to remove and re-add the whole tabbedbar, resulting in a flickering.
  7. Hans Knöchel 2019-02-04

    [~jquick] Can you please present a working workaround for this? The priority is only "medium" and severity "minor", which is not correct. This makes tabbed-bars in real apps useless, since no change of state (= index) can be applied after creation. This blocks our current Android beta version in which we already need to jump from one workaround to another, which is really frustrating already.
  8. Joshua Quick 2019-02-05

    I think dynamically setting the TabbedBar "labels" and "index" properties via the window's "open" event is the best work-around for the moment.
       var window = Ti.UI.createWindow();
       var tabbedBar = Ti.UI.createTabbedBar({
       	width: "80%",
       });
       window.add(tabbedBar);
       window.addEventListener("open", function() {
       	tabbedBar.labels = ["One", "Two", "Three"];
       	tabbedBar.index = 2;
       });
       window.open();
       
    Alternatively, you can add methods to the TabbedBar prototype like the below and update the properties via the initial "postlayout" event.
       // Add this to your "app.js" or "alloy.js" file.
       Ti.UI.TabbedBar.prototype.setLabelsWorkAround = function(labels) {
       	this.addEventListener("postlayout", function onLayout(e) {
       		e.source.removeEventListener(e.type, onLayout);
       		e.source.labels = labels;
       	});
       };
       Ti.UI.TabbedBar.prototype.setIndexWorkAround = function(index) {
       	this.addEventListener("postlayout", function onLayout(e) {
       		e.source.removeEventListener(e.type, onLayout);
       		e.source.index = index;
       	});
       };
       
       // Use the above methods when setting labels/index before window has opened.
       var window = Ti.UI.createWindow();
       var tabbedBar = Ti.UI.createTabbedBar({
       	width: "80%",
       });
       tabbedBar.setLabelsWorkAround(["One", "Two", "Three"]);
       tabbedBar.setIndexWorkAround(2);
       window.add(tabbedBar);
       window.open();
       
    What do you think? *_Edit:_* _I tried to override the prototype's "index" and "labels" properties via Object.defineProperty(), but these properties appear to be non-configurable when defined on the C++ side. I can't override them._
  9. Hans Knöchel 2019-02-05

    Unfortunately that is no suitable workaround for us. We need to change the properties after the user does certain actions, so the open event is too early.
  10. Samir Mohammed 2019-03-15

    FR Passed: Waiting on Jenkins builds.
  11. Hans Knöchel 2019-03-20

    As described on GitHub, this causes bad glitches in our app once the activity containing this feature is reopened. A video is attached (private scope). After checking this further, it seems to cause an infinite loop (likely because different to iOS), setting the index causes a change event which triggers it again. *EDIT*: I could isolate it to the "click" event being fired too many times. I still need to compare the concrete behavior with iOS, but it definitely should not fire if: - the tabbed-bar was just created - the tabs change - a click against a tab that is already selected is done
  12. Joshua Quick 2019-03-21

    [~hknoechel], I'm not sure how to reproduce your issue. I tested it with the below and it did *NOT* get stuck in an infinite loop.
        var window = Ti.UI.createWindow({
        	layout: "vertical",
        });
        var label = Ti.UI.createLabel({
        	top: "30%",
        });
        window.add(label);
        var tabbedBar = Ti.UI.createTabbedBar({
        	labels: ["One", "Two", "Three"],
        	top: "20dp",
        	width: "80%",
        });
        tabbedBar.addEventListener("click", function(e) {
        	var message = "Selected Tab Index: " + e.index;
        	Ti.API.info("@@@ " + message);
        	label.text = message;
        
        	// Setting the TabbedBar.index to same index does not cause infinite loop.
        	tabbedBar.index = e.index;
        });
        window.add(tabbedBar);
        window.addEventListener("focus", function() {
        	// Select tab "Two" when window opens and upon app resume.
        	tabbedBar.index = 1;
        });
        window.open();
        
  13. Samir Mohammed 2019-04-03

    *Closing ticket* Fix verified in SDK Version 8.0.1.v20190402094527 and SDK Version 8.1.0.v20190402113644 Test and other information can be found at: PR (master): https://github.com/appcelerator/titanium_mobile/pull/10646 PR (8.0.x): https://github.com/appcelerator/titanium_mobile/pull/10655 [~hknoechel] Feel free to reopen the ticket if you are still experiencing issues with steps on how to reproduce the issue.

JSON Source