Closed Code-DJ closed 6 months ago
Hi @adospace thanks for the quick fix. It appears there is still something missing. At least when I use the latest main branch.
The original issue is resolved - opening a modal dialog and closing it - the state continues to work. From the Main Page, if you navigate to the Interim Page and come back, the state stops working.
public class Bug179StartPage : Component
{
public override VisualNode Render() => new NavigationPage()
{
new ContentPage()
{
new Button("Move To Main Page")
.OnClicked(async () => await Navigation!.PushAsync<Bug179MainPage>())
}
};
}
public class Bug179MainPageState
{
public bool IsLabelVisible { get; set; }
}
public class Bug179MainPage : Component<Bug179MainPageState>
{
public override VisualNode Render()
{
return new ContentPage()
{
new StackLayout()
{
new Label()
.Text("Peek a Boo")
.IsVisible(State.IsLabelVisible),
new Button()
.Text("Toggle Label")
.OnClicked(() => SetState(state => state.IsLabelVisible = !state.IsLabelVisible)),
new Button("Go to Interim Page")
.OnClicked(async () => await Navigation!.PushAsync<Bug179InterimPage>()),
new Button("Open Child Page")
.OnClicked(async () => await Navigation!.PushModalAsync<Bug179ChildPage>())
}
.VCenter()
.HCenter()
}
.Title("Main Page");
}
}
public class Bug179InterimPage : Component
{
public override VisualNode Render()
{
return new ContentPage()
{
}
.Title("Interim Page");
}
}
public class Bug179ChildPage : Component
{
public override VisualNode Render() => new NavigationPage()
{
new ContentPage()
{
new Button("Back")
.VCenter()
.HCenter()
.OnClicked(async () => await Navigation!.PopModalAsync())
}
.Title("Child Page")
}
.OniOS(page => page.Set(MauiControls.PlatformConfiguration.iOSSpecific.Page.ModalPresentationStyleProperty, MauiControls.PlatformConfiguration.iOSSpecific.UIModalPresentationStyle.FormSheet));
}
Sorry, too rushed, looking again, this time I'll let you test the fix before publishing the new version 😉
Hi, the Main branch contains the fix for this nasty bug, please let me know if it still doesn't cover all the cases.
A bit of background and why it's so hard to completely fix this issue:
This is a serious bug that I introduced a few versions ago when I tried to fix an unwanted behavior of the component WillUnmount event (see #164).
The problem was that when you navigated away from a page (navigating back) you didn't receive the WillUnmount event on the component rendering the page.
For example, considering:
class myPageComponent : Component
{
override void OnWillUnmount()
{
//was never called
}
override OnRender()=> new ContentPage("my page");
}
My wrong understanding was that attaching to the Disappearing event of the page was enough to unmount the component. This was not the case. Even attaching to the Unload event of the page was sufficient because I just discovered the MAUI loads/unloads the same page when you navigate to and back to the page.
Now, I internal cache the list of pages created, and every time the developer navigates to a different page I check if any page has been removed from the navigation stack, and only then I unmount the component.
Thanks for having reported it
Yay! it works. Thank you for the quick turnaround and the detailed explanation 🙏.
Hi @adospace there is still a state related issue for modal dialogs - tried it on iOS - 2.0.5-beta.
Run the following with .UseMauiReactorApp<StartPage>()
:
public class StartPage : Component
{
public override VisualNode Render() => new NavigationPage()
{
new ContentPage()
{
new Button("Open Child Page")
.OnClicked(async () => await Navigation!.PushModalAsync<ChildPage>())
}
};
}
public class ChildPageState
{
public bool IsFlyoutExpanded { get; set; }
}
public class ChildPage : Component<ChildPageState>
{
public override VisualNode Render() => new NavigationPage()
{
new ContentPage(page =>
{
AddToolbarItem(page!, "Flyout", (s, e) => SetState(state => state.IsFlyoutExpanded = !state.IsFlyoutExpanded));
AddToolbarItem(page!, "Close", async (s, e) => await page!.Navigation!.PopModalAsync());
})
{
new Grid
{
new Flyout()
.IsExpanded(State.IsFlyoutExpanded)
.ExpandedChangedAction((isExpanded) => SetState(state => state.IsFlyoutExpanded = isExpanded))
}
}
.Title("Child Page")
.BackgroundColor(Colors.Aquamarine)
}
.OniOS(page => page.Set(MauiControls.PlatformConfiguration.iOSSpecific.Page.ModalPresentationStyleProperty, MauiControls.PlatformConfiguration.iOSSpecific.UIModalPresentationStyle.FormSheet));
MauiControls.ToolbarItem AddToolbarItem(MauiControls.Page page, string text, EventHandler? clicked = null)
{
if (!page.ToolbarItems.Any(i => i.Text == text))
{
var toolbarItem = new MauiControls.ToolbarItem
{
Text = text
};
if (clicked != null)
toolbarItem.Clicked += clicked;
page.ToolbarItems.Add(toolbarItem);
return toolbarItem;
}
return null!;
}
}
class FlyoutState : EntitySetPageState<Note>
{
public bool IsExpanded { get; set; }
}
class Flyout : Component<FlyoutState>
{
const int FlyoutAnimationSpeed = 200;
private bool isExpanded;
private Action<bool>? expandedChangedAction;
public Flyout IsExpanded(bool isExpanded)
{
this.isExpanded = isExpanded;
return this;
}
public Flyout ExpandedChangedAction(Action<bool>? expandedChangedAction)
{
this.expandedChangedAction = expandedChangedAction;
return this;
}
protected override void OnMountedOrPropsChanged()
{
base.OnMountedOrPropsChanged();
SetState(state => state.IsExpanded = isExpanded);
}
public override VisualNode Render()
{
var layout = new AbsoluteLayout
{
RenderBackdrop(),
RenderBody()
};
if (State.IsExpanded)
{
layout.HFill();
layout.VFill();
}
else
{
layout.WidthRequest(1);
layout.HeightRequest(1);
layout.VStart();
layout.HEnd();
}
return layout;
}
private Grid RenderBody() => new Grid
{
new Frame
{
new StackLayout()
{
new Button("Open Child Page")
.OnClicked(async () => await Navigation!.PushModalAsync<ChildPage>())
}
}
.WidthRequest(300)
.HeightRequest(400)
.HasShadow(false)
.BorderColor(Colors.LightGray)
}
.Padding(10)
.Opacity(State.IsExpanded ? 1 : 0)
.Scale(State.IsExpanded ? 1 : 0.8)
.WithAnimation(easing: Easing.SpringOut, duration: FlyoutAnimationSpeed)
.AbsoluteLayoutBounds(new Rect(1, 0, MauiControls.AbsoluteLayout.AutoSize, MauiControls.AbsoluteLayout.AutoSize))
.AbsoluteLayoutFlags(AbsoluteLayoutFlags.PositionProportional);
Grid RenderBackdrop() => new Grid()
.BackgroundColor(AppColors.PageBackgroundColor)
.Opacity(State.IsExpanded ? 0.5f : 0)
.WithAnimation(easing: Easing.CubicOut, duration: FlyoutAnimationSpeed)
.AbsoluteLayoutBounds(new Rect(0, 0, 1, 1))
.AbsoluteLayoutFlags(AbsoluteLayoutFlags.All)
.OnTapped(() =>
{
SetState(state => state.IsExpanded = false);
expandedChangedAction?.Invoke(State.IsExpanded);
});
}
Hi, thanks for reporting it (again). I've fixed the bug in the Beta-7 version online, please update your references and let me know if it's working for you too.
Side note, this is how to use ToolbarItems in MauiReactor:
public override VisualNode Render() => new NavigationPage()
{
new ContentPage()
{
new ToolbarItem("Flyout")
.OnClicked(()=>SetState(state => state.IsFlyoutExpanded = !state.IsFlyoutExpanded)),
new ToolbarItem("Close")
.OnClicked(()=>Navigation?.PopModalAsync()),
new Grid
{
new Bug179_2Flyout()
.IsExpanded(State.IsFlyoutExpanded)
.ExpandedChangedAction((isExpanded) => SetState(state => state.IsFlyoutExpanded = isExpanded))
}
}
.Title("Child Page")
.BackgroundColor(Colors.Aquamarine)
};
Hi!, it works with 2.0.7-beta. Thank you!. Also, thanks for the tip on Toolbar. Closing issue.
With a NavigationPage based routing (haven't tried with Shell), if you open a modal page using Navigation.PushModalAsync, the state on the parent page stops working.
The following is a modified version of the TestApp (2.0.0-beta + .net 8.0):