Skip to content

Commit

Permalink
Merge pull request #19 from felipebaltazar/feature/loading-error-temp…
Browse files Browse the repository at this point in the history
…late

Loading/Error template
  • Loading branch information
felipebaltazar authored Mar 3, 2024
2 parents 43d86e7 + 41a4702 commit f7d3e9b
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 19 deletions.
22 changes: 9 additions & 13 deletions Maui.ServerDrivenUI/Abstractions/IServerDrivenVisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@

public interface IServerDrivenVisualElement
{
public string? ServerKey
{
get; set;
}
string? ServerKey { get; set; }

public UIElementState State
{
get; set;
}
public Action OnLoaded
{
get;
set;
}
UIElementState State { get; set; }

Action? OnLoaded { get; set; }

DataTemplate LoadingTemplate { get; set; }

DataTemplate ErrorTemplate { get; set; }

void OnStateChanged(UIElementState newState);

void OnError(Exception ex);
}
26 changes: 25 additions & 1 deletion Maui.ServerDrivenUI/Views/ServerDrivenContentPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ public abstract class ServerDrivenContentPage : ContentPage, IServerDrivenVisual
typeof(ServerDrivenContentPage),
UIElementState.None,
propertyChanged: ServerDrivenVisualElement.OnStatePropertyChanged);

public static readonly BindableProperty LoadingTemplateProperty = BindableProperty.Create(
nameof(LoadingTemplate),
typeof(DataTemplate),
typeof(ServerDrivenView),
null);

public static readonly BindableProperty ErrorTemplateProperty = BindableProperty.Create(
nameof(ErrorTemplate),
typeof(DataTemplate),
typeof(ServerDrivenView),
null);

#endregion

Expand All @@ -35,12 +47,24 @@ public UIElementState State
set => SetValue(StateProperty, value);
}

public Action OnLoaded
public Action? OnLoaded
{
get;
set;
}

public DataTemplate LoadingTemplate
{
get => (DataTemplate)GetValue(LoadingTemplateProperty);
set => SetValue(LoadingTemplateProperty, value);
}

public DataTemplate ErrorTemplate
{
get => (DataTemplate)GetValue(ErrorTemplateProperty);
set => SetValue(ErrorTemplateProperty, value);
}

#endregion

#region Constructors
Expand Down
24 changes: 24 additions & 0 deletions Maui.ServerDrivenUI/Views/ServerDrivenView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ public class ServerDrivenView : ContentView, IServerDrivenVisualElement
UIElementState.None,
propertyChanged: ServerDrivenVisualElement.OnStatePropertyChanged);

public static readonly BindableProperty LoadingTemplateProperty = BindableProperty.Create(
nameof(LoadingTemplate),
typeof(DataTemplate),
typeof(ServerDrivenView),
null);

public static readonly BindableProperty ErrorTemplateProperty = BindableProperty.Create(
nameof(ErrorTemplate),
typeof(DataTemplate),
typeof(ServerDrivenView),
null);

#endregion

#region Properties
Expand All @@ -35,6 +47,18 @@ public UIElementState State
set => SetValue(StateProperty, value);
}

public DataTemplate LoadingTemplate
{
get => (DataTemplate)GetValue(LoadingTemplateProperty);
set => SetValue(LoadingTemplateProperty, value);
}

public DataTemplate ErrorTemplate
{
get => (DataTemplate)GetValue(ErrorTemplateProperty);
set => SetValue(ErrorTemplateProperty, value);
}

public Action? OnLoaded
{
get;
Expand Down
84 changes: 84 additions & 0 deletions Maui.ServerDrivenUI/Views/ServerDrivenVisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ internal class ServerDrivenVisualElement

internal static async Task InitializeComponentAsync(IServerDrivenVisualElement element, int attempt = 0)
{
var errorMessage = string.Empty;
try
{
ShowLoadingView(element);
MainThread.BeginInvokeOnMainThread(() => element.State = UIElementState.Loading);

var serverDrivenUiService = ServiceProviderHelper
Expand Down Expand Up @@ -60,6 +62,8 @@ internal static async Task InitializeComponentAsync(IServerDrivenVisualElement e
}
else
{
errorMessage = SERVICE_NOT_FOUND;

MainThread.BeginInvokeOnMainThread(() => {
element.State = UIElementState.Error;
element.OnError(new DependencyRegistrationException(SERVICE_NOT_FOUND));
Expand All @@ -68,11 +72,17 @@ internal static async Task InitializeComponentAsync(IServerDrivenVisualElement e
}
catch (Exception ex)
{
errorMessage = ex.Message;
MainThread.BeginInvokeOnMainThread(() => {
element.State = UIElementState.Error;
element.OnError(ex);
});
}

if (!string.IsNullOrWhiteSpace(errorMessage))
{
ShowErrorView(element, errorMessage);
}
}

private static bool IsXamlLoaded(IServerDrivenVisualElement element, int attempt)
Expand All @@ -96,6 +106,50 @@ private static bool IsXamlLoaded(IServerDrivenVisualElement element, int attempt
return true;
}

private static void ShowLoadingView(IServerDrivenVisualElement element)
{
View loadingView = (element.LoadingTemplate?.CreateContent() as View)
?? CreateDefaultLoadingTemplate();

SetContent(element, loadingView);
}

private static void ShowErrorView(IServerDrivenVisualElement element, string errorMessage)
{
try
{
View errorView = (element.ErrorTemplate?.CreateContent() as View)
?? CreateDefaultErrorTemplate(errorMessage);

SetContent(element, errorView);
}
catch (Exception ex)
{
MainThread.BeginInvokeOnMainThread(() => {
element.State = UIElementState.Error;
element.OnError(ex);
});
}
}

private static void SetContent(IServerDrivenVisualElement element, View template)
{
if (element is ContentView contentView && template != null)
{
MainThread.BeginInvokeOnMainThread(() => {
template.BindingContext = contentView.BindingContext;
contentView.Content = template;
});
}
else if (element is ContentPage contentPage && template != null)
{
MainThread.BeginInvokeOnMainThread(() => {
template.BindingContext = contentPage.BindingContext;
contentPage.Content = template;
});
}
}

internal static void OnStatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is not IServerDrivenVisualElement view
Expand All @@ -106,4 +160,34 @@ internal static void OnStatePropertyChanged(BindableObject bindable, object oldV
if (newState is UIElementState.Loaded)
view.OnLoaded?.Invoke();
}

internal static View CreateDefaultLoadingTemplate() =>
new Frame() {
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
BackgroundColor = Colors.Black,
BorderColor = Colors.Transparent,
Content = new ActivityIndicator() {
IsRunning = true,
IsEnabled = true,
Color = Colors.White,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
}
};

internal static View CreateDefaultErrorTemplate(string errorMessage) =>
new Frame() {
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
BackgroundColor = Colors.Red,
BorderColor = Colors.Transparent,
Content = new Label {
Text = errorMessage,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center
}
};
}
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public static class MauiProgram
}
```

- You can now use the ServerDrivenUI Elements
- You can now use the ServerDrivenUI Elements, defining the key that will be used to get the UI from the API
- You can also define a LoadingTemplate and an ErrorTemplate to be shown while the UI is being fetched from the API

```csharp

```xaml
<?xml version="1.0" encoding="utf-8" ?>
Expand All @@ -53,7 +56,23 @@ public static class MauiProgram
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

<ServerDrivenView x:Name="sduiView" ServerKey="MyView" />
<ServerDrivenView x:Name="sduiView" ServerKey="MyView">
<ServerDrivenView.ErrorTemplate>
<DataTemplate>
<StackLayout>
<Label Text="Unexpected error" />
</StackLayout>
</DataTemplate>
</ServerDrivenView.ErrorTemplate>

<ServerDrivenView.LoadingTemplate>
<DataTemplate>
<StackLayout>
<Label Text="Loading..." />
</StackLayout>
</DataTemplate>
</ServerDrivenView.LoadingTemplate>
</ServerDrivenView>

</ContentPage>

Expand All @@ -67,4 +86,4 @@ We are currently doing a [workaround](https://github.com/felipebaltazar/Maui.Ser
## Repo Activity

![Alt](https://repobeats.axiom.co/api/embed/e3457a9dc9131c33ca38ceb2203bfffa67864080.svg "Repobeats analytics image")
![Alt](https://repobeats.axiom.co/api/embed/e3457a9dc9131c33ca38ceb2203bfffa67864080.svg "Repo activity analytics image")
18 changes: 17 additions & 1 deletion samples/Maui.ServerDrivenUI.Sample/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

<ServerDrivenView x:Name="sduiView" ServerKey="MyView" />
<ServerDrivenView x:Name="sduiView" ServerKey="MyView">
<ServerDrivenView.ErrorTemplate>
<DataTemplate>
<StackLayout>
<Label Text="Unexpected error" />
</StackLayout>
</DataTemplate>
</ServerDrivenView.ErrorTemplate>

<ServerDrivenView.LoadingTemplate>
<DataTemplate>
<StackLayout>
<Label Text="Loading..." />
</StackLayout>
</DataTemplate>
</ServerDrivenView.LoadingTemplate>
</ServerDrivenView>

</ContentPage>
1 change: 0 additions & 1 deletion samples/Maui.ServerDrivenUI.Sample/ViewModelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ protected void OnPropertyChanged(string propertyName) =>

protected void Set<T>(ref T field, T value, string propertyName)
{

if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
Expand Down

0 comments on commit f7d3e9b

Please sign in to comment.