Master/detail with editing: two forms or one form?

116 views
Skip to first unread message

P5music

unread,
Sep 11, 2020, 8:41:56 AM9/11/20
to CodenameOne Discussions
My Codename app has master/detail layout.
According to orientation the detail (editing) or the master can stay full screen.
I have to handle many orientation and editing status configurations, so sometimes the editing screen will be displayed out of the master screen in portrait orientation.

The back navigation (button and other gestures) has to be managed in both cases.

I am not sure if I have to use a main form and two containers, or instead two forms, that is, also an EditingForm with the EditingContainer inside.
Thanks in advance


Shai Almog

unread,
Sep 12, 2020, 1:04:13 AM9/12/20
to CodenameOne Discussions
In some cases you need two forms and in some cases two containers.
The trick for doing this is to work with two containers and when necessary wrap them in a Form to enable the two page master detail.

P5music

unread,
Sep 12, 2020, 5:31:31 AM9/12/20
to CodenameOne Discussions
Thanks, do you mean having two forms side by side, that expands when in portrait mode going full screen singularly, or you mean that I open another form on top of the main form?
I started creating an editing container that can be added to the main form or to the editing form, so I remove all and then add again. Is this the right way to do that?

Shai Almog

unread,
Sep 12, 2020, 9:43:59 PM9/12/20
to CodenameOne Discussions
I mean having containers side by side in one form (see the kitchen sink where we do just that).
I don''t recommend having a form embedded in a form. Forms are very "heavy" and things sometimes fail when you add a form inside another form. Historically this was prohibited (we'd throw an exception in that case) but some use cases for embedding a form do exist. I'd still avoid it when possible.

P5music

unread,
Sep 14, 2020, 5:48:15 PM9/14/20
to CodenameOne Discussions
My app needs to manage cases where a back navigation occurs but not toward the original master/detail form, indeed to a different orientation of the editing view itself.
For example the editing view in portrait mode is fullscreen and can undergo a device rotation: in that case the landscape mode is the new full screen mode for the editing view, while master/detail is reached back only when further back navigation occurs when the user tap the back button.

So I thought this has to be accompished with a new form: the editing form, to which the editing container is added, because I think it is the right way to manage back navigation, that is. navigation is between forms, back and forth.

I ask whether the editing container has to be recreated, or it can removed from the master/detail form and then added to the editing form.
Thanks

Shai Almog

unread,
Sep 14, 2020, 9:50:41 PM9/14/20
to CodenameOne Discussions
You can use setBackCommand and override the hardware back command to have any functionality you want. It can just replace containers and then eventually move a form. You obviously need to keep track of everything which isn't simple.

P5music

unread,
Sep 15, 2020, 3:39:40 AM9/15/20
to CodenameOne Discussions
So your advice is to use one form for the master/detai in every configuration, just managing the container, with the override of the back button and using the back command to manage the container inside the form.
So I have not to do this:

if (isPortrait() && (conditionsAreTrue))
{
new EditingForm(appData,myData,mainForm,editingContainer,other parameters).show();
}
(this is what I am trying to do now)

the form will be just used when a new screen has to be presented, like the help form.
Is it right?

Shai Almog

unread,
Sep 15, 2020, 10:40:29 PM9/15/20
to CodenameOne Discussions
My advice is to use Containers for everything but have global navigation logic. So when you need to show a UI element you can invoke that single place that decides.

If this is on a phone you would create a Form add the container to it and set its title etc. then show.
If this is a tablet you would replace the last container.and update the subtitle in the UI

P5music

unread,
Sep 16, 2020, 2:54:57 AM9/16/20
to CodenameOne Discussions
What you say seems to be merging the two things, like I am already doing? Indeed my app is for tablet and phone, so my question was just about the management of these two scenarios, and the orientation too.

Is this what you are advicing?

  if (isPortrait() && (editingConditions))
        {
            new EditingForm(appData,myData,mainForm,editingContainer,otherParameters).show();
            setEditor();

        if (isPortrait() && !(editingConditions)
        {
            removeContainers();
            mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(1*100)),fab.bindFabToContainer(masterContainer));
        }
        
        if(isTablet() && !isPortrait() )
        {

           removeContainers();
            ((EditingContainer)editingContainer).setCallingForm(mainForm);
            mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(leftContainerRatio*100)),fab.bindFabToContainer(masterContainer))
                    .add( editingContainer);
            if (editingConditions) setEditor();

        }

Thanks in advance

Shai Almog

unread,
Sep 17, 2020, 12:15:49 AM9/17/20
to CodenameOne Discussions
Look sat the code for kitchen sink. Notice we encapsulate each demo in a Demo class. Then in the main KitchenSink class we show either a Form or a Container based on the mode (tablet or phone). Yes it's pretty similar to this.

P5music

unread,
Sep 17, 2020, 5:07:41 AM9/17/20
to CodenameOne Discussions
I created a method like the one you can see below. It is called at startup and it works. It is also called in the orientation change listener.

But when the orientation changes I see that the user interface is cleaned up and no containers appear, that is, the new rotated interface does not appear, does not form, it is blank.
If there is no call to the remove() methods I get errors like "already added", and it is reasonable because the containers don't get removed.
So the right version has to include the remove() calls. 

Hence, I think the code is adding the right containers as it happens at startup but the user interface is not shown. Why? 

private void setContainersAndForm()
{
editingContainer.remove();
itemListContainer.remove();
fab.remove();

 if (isPortrait() && (editingConditions))
        {
            new EditingForm(appData,myData,mainForm,editingContainer,otherParameters).show();
            setEditor();

        if (isPortrait() && !(editingConditions)
        {
            removeContainers();
            mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(1*100)),fab.bindFabToContainer(masterContainer));
        }
        
        if(isTablet() && !isPortrait() )
        {

           removeContainers();
            ((EditingContainer)editingContainer).setCallingForm(mainForm);
            mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(leftContainerRatio*100)),fab.bindFabToContainer(masterContainer))
                    .add( editingContainer);
            if (editingConditions) setEditor();

        }

// also with these two lines
//mainEditingContainer.revalidate();
//itemListContainer.revalidate();

mainForm.revalidate();
}

Shai Almog

unread,
Sep 18, 2020, 12:15:30 AM9/18/20
to CodenameOne Discussions
Look at the console when debugging this in the simulator. You would probably see exceptions indicating you can't add a component to a Container if it's already added to some container (even if it's the same container). Yes, you need to adapt the code to handle that logic. Orientation change re-layout needs to be slightly different from first showing of the UI. I'd also recommend an animation on orientation change which is something we don't need/want for the first showing of the form.

P5music

unread,
Sep 18, 2020, 3:43:10 AM9/18/20
to CodenameOne Discussions
I only get 
Exception in thread "AWT-EventQueue-0" Failed to get location on screen:component must be showing on the screen to determine its location
Failed to get location on screen:component must be showing on the screen to determine its location
Indeed in my code the removal is performed:
editingContainer.remove();
itemListContainer.remove();
fab.remove();

So what could be the reason?

Shai Almog

unread,
Sep 19, 2020, 1:41:29 AM9/19/20
to CodenameOne Discussions
Do you see the full stack trace?
It should point at a specific line. What's added at that line?
That component should be removed.

The FAB could be problematic since FAB is added in a unique way. Is it added to the form or an arbitrary container?

P5music

unread,
Sep 19, 2020, 5:30:49 AM9/19/20
to CodenameOne Discussions
No back trace is available, just the text you have read in the previous post.
The fab is only added to mainForm and bound to masterContainer (you can see in the code snippet above) but
even if I do not have the fab at all in the application the error is issued:
Exception in thread "AWT-EventQueue-0" Exception in thread "AWT-EventQueue-0" Exception in thread "AWT-EventQueue-0" Exception in thread "AWT-EventQueue-0" Failed to get location on screen:component must be showing on the screen to determine its location

No line is highlighted in the source code because no Java exception is issued.

Shai Almog

unread,
Sep 20, 2020, 1:18:05 AM9/20/20
to CodenameOne Discussions
OK I see this printout. Do you have CEF enabled?
Is there a browser component somewhere?
This error should be meaningless and shouldn't impact the behavior of the app, it's related to the simulator.

P5music

unread,
Sep 20, 2020, 8:16:12 AM9/20/20
to CodenameOne Discussions
Yeah, I just provided the error because you asked about, I just would like to know why the user interface is not reconstructed after orientation change. The provided mehod above is called both at startup and when an orientation change occurs, but in the latter case it fails in recreating the user interface.
Regards

Shai Almog

unread,
Sep 20, 2020, 10:35:07 PM9/20/20
to CodenameOne Discussions
I assumed incorrectly that you're adding a component that's already added. I think what's happening is that you removed the components before the re-layout then added them after. You would need to do either an animateLayout() or a revalidate() in this case.

P5music

unread,
Sep 21, 2020, 4:51:20 AM9/21/20
to CodenameOne Discussions
It's very strange. I had to add a revalidate() also after removing the containers.
I do not know if it is intended behaviour but this code works (see also below this code, there is the second part of this message about fab breaking it):

private void setContainers()
{

mainEditingContainer.remove();
itemListContainer.remove();
//fab.remove();


mainEditingContainer.revalidate();
itemListContainer.revalidate();
mainForm.revalidate();

if (isPortrait() && (other conditions))
{
new EditingForm(appData,myData,mainForm,editingContainer,other Parameters).show();

}

if (isPortrait() && !(other conditions))
{
//mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(1*100)),fab.bindFabToContainer(itemListContainer));
mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(1*100)),itemListContainer);
}

if(isTablet() && !isPortrait() ) {

/*mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int) (leftContainerRatio * 100)), fab.bindFabToContainer(itemListContainer))
.add(mainEditingContainer);*/
mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int) (leftContainerRatio * 100)), itemListContainer)
.add(mainEditingContainer);

}

}

mainEditingContainer.revalidate();
itemListContainer.revalidate();
mainForm.revalidate();
}
------

But now the fab problem is back. Indeed if I use this code


private void setContainers()
{

mainEditingContainer.remove();
itemListContainer.remove();

if (!fabFirstTime) fab.remove();


mainEditingContainer.revalidate();
itemListContainer.revalidate();
mainForm.revalidate();

if (isPortrait() && (conditions)
{
new EditingForm(appData,myData,mainForm,editingContainer,other parameters).show();

}

if (isPortrait() && !(conditions)
{
mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int)(1*100)),fab.bindFabToContainer(itemListContainer));
}

if(isTablet() && !isPortrait() ) {

mainForm.add(tl.createConstraint().heightPercentage(100).widthPercentage((int) (leftContainerRatio * 100)), fab.bindFabToContainer(itemListContainer)).add(mainEditingContainer);

}

mainEditingContainer.revalidate();
itemListContainer.revalidate();
mainForm.revalidate();

if (fabFirstTime) fabFirstTime=false;

}

the user interface is not reconstructed again and no button is visible.
If I do not remove the fab I get the error 
Component is already contained in Container: Container[x=0 y=0 width=1136 height=1920 name=null, layout = FlowLayout, scrollableX = false, scrollableY = false, components = [FloatingActionButton]]
but I can see the button (in an wrong position because it is the old x position I think, indeed orientation change has occurred).
Thanks in advance

Shai Almog

unread,
Sep 22, 2020, 10:17:41 PM9/22/20
to CodenameOne Discussions
A single revalidate on the main form should work fine. Doing additional revalidates might cause them to disrupt one another.
Notice that you might want to use the version of revalidate that plays nicely with animations.

P5music

unread,
Sep 23, 2020, 3:44:04 AM9/23/20
to CodenameOne Discussions
I just can use this workaround, because otherwise it does not work. So I have to call revalidate() in two differents steps.
Said that, now the problem is the floating action button. As in the second example there is an error issued.
What's wrong?
Regards

Shai Almog

unread,
Sep 23, 2020, 10:41:33 PM9/23/20
to CodenameOne Discussions
Fab is a bit problematic in that sense. You don't add it to a container. You bind it.
It also acts differently depending on your target container, see this recent discussion: https://stackoverflow.com/questions/63845751/codenameone-floating-action-button-binding-error/63856717

P5music

unread,
Sep 24, 2020, 3:08:30 AM9/24/20
to CodenameOne Discussions
Yes, I remember that discussion, I also adopted your solution, but the fab now is bound to a form, so why is the runtime complaining?
If fab is problematic, what's the solution? Is this an issue?
Thanks

Shai Almog

unread,
Sep 24, 2020, 11:55:18 PM9/24/20
to CodenameOne Discussions
FAB wasn't designed for form adding/removing. There's a bit of logic there. I'll try to add an unbind() method for next weeks update.

P5music

unread,
Sep 26, 2020, 5:44:58 AM9/26/20
to CodenameOne Discussions
Thank you, but no form is added or removed, it is just the orientation change happening.

Shai Almog

unread,
Sep 27, 2020, 1:55:19 AM9/27/20
to CodenameOne Discussions
In that case I will need a test case that reproduces the problem. I understood you relayout with an orientation listener from previous comments.

P5music

unread,
Sep 27, 2020, 12:35:42 PM9/27/20
to CodenameOne Discussions
The attached zip files contains the src files from a simplified project that reproduces the issue. 
I couldn't attach the entire project because the zip files was too big. It is a Hello world project.
Regards

src.zip

Shai Almog

unread,
Sep 28, 2020, 1:34:48 AM9/28/20
to CodenameOne Discussions
Please file this as an issue and include the source code embedded into the issue so we can assign/track/read it easily.
Reply all
Reply to author
Forward
0 new messages