Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-28459] Android: ScrollableView with databinding in Footer/HeaderView not showing up

GitHub Issuen/a
TypeBug
Priorityn/a
StatusClosed
ResolutionFixed
Resolution Date2021-06-28T22:16:09.000+0000
Affected Version/sn/a
Fix Version/sRelease 10.0.1
ComponentsAndroid
Labelsn/a
ReporterMichael Gangolf
AssigneeGary Mathews
Created2021-05-20T11:43:28.000+0000
Updated2021-07-07T01:57:50.000+0000

Description

When you add a ScrollableView inside a HeaderView with databinding the child views aren't visible. I've rebuild the generated classic code from my Alloy app to make it easier to test but here is the original Alloy part first: For my initial test I've used mocx (https://github.com/jasonkneen/mocx/blob/master/mocx.js) inside the lib folder and this code inside alloy.js:
var mocx = require("/mocx");
mocx.createCollection("contacts", [{name : "John Smith"}, {name : "Jane Doe"}]);
my xml:
<Alloy>
    <Window fullscreen="true">
        <ListView>
            <ListSection dataCollection="contacts" bottom="200">

                <HeaderView>
                    <View backgroundColor="#DDD" height="200">
                        <ScrollableView height="200" dataCollection="contacts" >
                            <!-- doesn't work -->
                            <View backgroundColor="red" height="200"/>
                        </ScrollableView>
                    </View>
                </HeaderView>

                <ListItem color="#000" title="{name}" searchableText="{name}"/>

                <FooterView>
                    <View backgroundColor="#DDD" height="Ti.UI.SIZE">
                        <ScrollableView height="200" dataCollection="contacts">
                            <!-- doesn't work -->
                            <View backgroundColor="red" height="200"/>
                        </ScrollableView>
                    </View>
                </FooterView>
            </ListSection>
        </ListView>

        <ScrollableView height="200" dataCollection="contacts" bottom="0">
            <ImageView image="/appicon.png"/>
        </ScrollableView>
    </Window>
</Alloy>
my controller
Alloy.Collections.contacts.fetch();
$.index.open();
the generated classic code looks like this:
for (var __alloyId7, opts = __alloyId11.opts || {}, models = __alloyId10.models, len = models.length,  views = [], i = 0; i < len; i++) {
    __alloyId7 = models[i], __alloyId7.__transform = transform(__alloyId7);
    var __alloyId9 = Ti.UI.createView({
        views: __alloyId6,
        apiName: "Ti.UI.ScrollableView",
        height: 200,
        classes: [],
        backgroundColor: "red"
    });
    views.push(__alloyId9);
}
$.__views.__alloyId5.views = views
It is adding the views to an array and set it to scrollableview.views. So I've created a classic example that works in the same way (without databinding) which makes it easier to test:
const win = Ti.UI.createWindow({
	backgroundColor: '#fff',
});

const sections = [];

var hv = Ti.UI.createView({
	height: 100,
	backgroundColor: "blue"
});

var sv = Ti.UI.createScrollableView({
	height: 100,
	backgroundColor: "yellow"
})
var views = [];
for (var i = 0; i < 10; ++i) {
	var insideView = Ti.UI.createView({
		height: 100,
		backgroundColor: "rgba(" + (i * 20) + "," + i + "," + i + ")"
	});
    var lbl = Ti.UI.createLabel({
        text:"should be visible",
        color: "#fff"
    })
    insideView.add(lbl);
	sv.addView(insideView)
}
hv.add(sv);
for (let i = 0; i < 3; i++) {
	const items = [];

	for (let x = 0; x < 5; x++) {
		items.push({
			label: {
				text: Item #${x}
			}
		});
	}

	sections.push(Ti.UI.createListSection({
		headerView: hv,
		items
	}));
}
const listView = Ti.UI.createListView({
	templates: {
		template: {
			properties: {
				backgroundColor: 'transparent'
			},
			childTemplates: [{
				type: 'Ti.UI.Label',
				bindId: 'label',
				properties: {
					left: 10,
					color: 'black'
				}
			}]
		}
	},
	backgroundColor: "#b3b3b3",
	defaultItemTemplate: 'template',
	sections: sections
});
win.add(listView);
win.open();
The result is the same: the scrollable child views won't appear. If I'm not using the array and use sv.addView(insideView) inside the loop it works fine! If I compare both UIs in Android Studio I can see that the childviews are not added: using views = array !no_childviews.png|thumbnail! using addView !childviews.png|thumbnail! *Setup:* * Android 11/Pixel 4 * Titanium SDK 10.1.0/10.0.0.GA

Attachments

FileDateSize
childviews.png2021-05-20T11:41:41.000+0000134489
no_childviews.png2021-05-20T11:41:41.000+000094525
ScrollableViewAddRemoveTest.js2021-05-26T00:09:01.000+00003040

Comments

  1. Michael Gangolf 2021-05-20

    I'm currently debugging TiUIScrollableView.java to see if there is something wrong in the setViews code
  2. Michael Gangolf 2021-05-20

    Turns out that calling addView after the loop:
       sv.addView(Ti.UI.createView({
            height: 100,
            backgroundColor: "green"
       }))
       
    will make it work. And running Alloy.Collections.contacts.fetch(); in the Alloy example after the ListView is visible works, too. Might be some timing issue somewhere This is some debug output when using Alloy.Collection.fetch(): {noformat} Proxy: views property <--- setViews() from ScrollableViewProxy process properties; views len: 0 clear views set views; len: 0 Proxy: handle message clear views set views; len: 2 Proxy: views property <--- setViews() from ScrollableViewProxy Proxy: handle message clear views set views; len: 2 release list <---- release() from TiUIScrollableView process properties; views len: 0 clear views set views; len: 0 {noformat} I have two elements in my collection so len:2 is correct. I'm not sure why the whole setViews from ScrollableViewProxy is called twice! Then there is the release part from TiUIScrollableView at the bottom that is clearing the content at the end.
  3. Joshua Quick 2021-05-20

    I haven't dived into this yet, but I'm wondering if it has something to do with us "updating" the HeaderView's activity object if it doesn't match the ListView's assigned activity object. We do this as of Titanium 10.0.0 so that dark/light theme switching will be applied to all child views which involves swapping out the old destroyed activity context with the new one. https://github.com/appcelerator/titanium_mobile/blob/master/android/modules/ui/src/java/ti/modules/titanium/ui/widget/listview/ListViewHolder.java#L302-L307 I'm "guessing" that the ScrollableView or its children were initially assigned a different activity context. Like the previous activity window such as the splash and it's being swapped out immediately once the actual activity window that's hosting the ListView has been created/opened.
  4. Michael Gangolf 2021-05-20

    Your guess looks like the right direction: I've added
       Log.i("----", "Context: " + context + " headerproxy act:" + headerProxy.getActivity());
       
       if ((context instanceof Activity) && (headerProxy.getActivity() != context)) {
       	Log.i("---", "release views");
       	headerProxy.releaseViews();
       	headerProxy.setActivity((Activity) context);
       }
       
    and got: {noformat} Context: org.appcelerator.titanium.TiActivity@92c8bbb headerproxy act:com.miga.random.TestAppActivity@af14391 release views release list process properties; views len: 0 clear views set views; len: 0 {noformat} This is AFTER the first the setViews part.
  5. Gary Mathews 2021-05-25

    master: https://github.com/appcelerator/titanium_mobile/pull/12839
  6. Christopher Williams 2021-06-28

    merged to master
  7. Ewan Harris 2021-06-28

    10_0_X backport merged

JSON Source