Closed mario-zelger closed 1 month ago
Hi, nothing wrong with your approach that works most of the time but the BottomSheet is a "special" control that works best using an imperative code.
This is why I've created a repo for that (that repo contains implementations for a variety of 3rd party controls): https://github.com/adospace/mauireactor-integration/blob/main/The49/Pages/BottomSheetManager.cs
I have used BottomSheet in some of my applications. this is one of them: https://github.com/adospace/mauireactor-samples/tree/main/TaskApp
https://www.youtube.com/watch?v=q-oM2PO0ZtU&ab_channel=AdolfoMarinucci
You're amazing! Thank you very much!
Hi @adospace
This might be out of scope as it relates to a 3rd party library.
I'm currently trying to integrate a BottomSheet with the help of The49.Maui.BottomSheet. To achieve this, I used your input from the documentation via How to deal with custom dialogs/popups?.
The code I've implemented for the scaffolding / wrapping part looks like the following.
Bottom Sheet Scaffold / Wrapper
```csharp [Scaffold(typeof(The49.Maui.BottomSheet.BottomSheet))] public partial class BottomSheet { protected override void OnAddChild(VisualNode widget, MauiControls.BindableObject childNativeControl) { if (childNativeControl is MauiControls.View content) { NativeControl.EnsureNotNull(); NativeControl.Content = content; } base.OnAddChild(widget, childNativeControl); } protected override void OnRemoveChild(VisualNode widget, MauiControls.BindableObject childNativeControl) { NativeControl.EnsureNotNull(); if (childNativeControl is MauiControls.View content && NativeControl.Content == content) { NativeControl.Content = null; } base.OnRemoveChild(widget, childNativeControl); } } public class BottomSheetHost : Component { private bool _isShown; private Action? _onDismissedAction;
private The49.Maui.BottomSheet.BottomSheet? _bottomSheet;
private readonly Action? _nativePopupCreateAction;
public BottomSheetHost(Action? nativePopupCreateAction = null)
{
_nativePopupCreateAction = nativePopupCreateAction;
}
public BottomSheetHost IsShown(bool isShown)
{
_isShown = isShown;
return this;
}
public BottomSheetHost OnDismissed(Action action)
{
_onDismissedAction = action;
return this;
}
protected override void OnMounted()
{
InitializeBottomSheet();
base.OnMounted();
}
protected override void OnPropsChanged()
{
InitializeBottomSheet();
base.OnPropsChanged();
}
private void InitializeBottomSheet()
{
if (!_isShown || MauiControls.Application.Current == null)
{
return;
}
MauiControls.Application.Current.Dispatcher.DispatchAsync(async () =>
{
if (ContainerPage == null || _bottomSheet is null)
{
return;
}
await _bottomSheet.ShowAsync();
});
}
public override VisualNode Render()
{
if (!_isShown)
{
return null!;
}
var children = Children();
return new BottomSheet(r =>
{
_bottomSheet = r;
_nativePopupCreateAction?.Invoke(r);
})
{
children[0]
}
.Detents([
new MediumDetent()
])
.SelectedDetent(new MediumDetent())
.OnDismissed((_, origin) => _onDismissedAction?.Invoke(origin));
}
}
```
Additionaly, here are parts of the app code I've used.
App Code
**MauiProgram.cs** ```csharp public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiReactorApp()
.UseBottomSheet()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}
```
**MainPage.cs**
```csharp
public class MainPage : Component
{
public override VisualNode Render() => NavigationPage(new BottomSheetPage());
}
```
**BottomSheetPage.cs**
```csharp
public class BottomSheetPage : Component
{
private The49.Maui.BottomSheet.BottomSheet? _bottomSheet;
public override VisualNode Render()
{
return new ContentPage
{
new Grid
{
new Button("Show Bottom Sheet")
.HCenter()
.VCenter()
.OnClicked(() => ShowBottomSheet()),
new BottomSheetHost(r => _bottomSheet = r)
{
new VStack(spacing: 10)
{
new Label("Hello from Bottom Sheet!"),
new Button("Close", async () => await _bottomSheet!.DismissAsync())
.HCenter()
.VCenter()
}
}
.IsShown(State.IsShown)
.OnDismissed(result =>
{
Console.WriteLine(result);
SetState(s => s.IsShown = false);
})
}
};
}
private void ShowBottomSheet()
{
if (State.IsShown)
{
return;
}
SetState(s => s.IsShown = true);
}
}
```
I now run into the problem that the .NET MAUI handlers from the BottomSheet implementation are being called during the
Render()
phase. At this stage certain properties are stillnull
which the handler expects to be non-null
. For example in this method theController
is null becauseShowAsync
has not yet been called.This leads to
NullReferenceException
s which can prevent the rendering of the content inside the BottomSheet.In a classic MVVM application the samples show that a custom BottomSheet is instantiated in a
Command
and then directly displayed viaShowAsync
.This way the mentioned
Controller
property is never null as the controller is created inside theShowAsync
method which happens before the component is acually rendered to the page (see here).Is there a way to achieve the same behavior with MAUI Reactor?
Thanks a lot for your help!