Skip to content

Commit

Permalink
Implement iOS background http call support
Browse files Browse the repository at this point in the history
  • Loading branch information
albyrock87 committed Oct 8, 2024
1 parent 3b9d184 commit d3d7626
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 3 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@

`Nalu.Maui` provides a set of classes to help you with everyday challenges encountered while working with .NET MAUI.

### Core [![Nalu.Maui.Core NuGet Package](https://img.shields.io/nuget/v/Nalu.Maui.Core.svg)](https://www.nuget.org/packages/Nalu.Maui.Core/) [![Nalu.Maui NuGet Package Downloads](https://img.shields.io/nuget/dt/Nalu.Maui.Core)](https://www.nuget.org/packages/Nalu.Maui.Core/)

The core library is intended to provide a set of common use utilities.

Have you ever noticed that when the user backgrounds the app on iOS, the app is suspended, and the network requests will fail due to `The network connection was lost`?

This is really annoying: it forces us to implement complex retry logic, especially considering that the request may have already hit the server.

To solve this issue, we provide a `NSUrlBackgroundSessionHttpMessageHandler` to be used in your `HttpClient` to allow http request to continue even when the app is in the background.

```csharp
#if IOS
var client = new HttpClient(new NSUrlBackgroundSessionHttpMessageHandler());
#else
var client = new HttpClient();
#endif
```

To make this work, you need to change your `AppDelegate` as follows:
```csharp
[Export("application:handleEventsForBackgroundURLSession:completionHandler:")]
public virtual void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
=> NSUrlBackgroundSessionHttpMessageHandler.HandleEventsForBackgroundUrl(application, sessionIdentifier, completionHandler);
```

**Check out the [Core Wiki](core.html) for more information**.

### Navigation [![Nalu.Maui.Navigation NuGet Package](https://img.shields.io/nuget/v/Nalu.Maui.Navigation.svg)](https://www.nuget.org/packages/Nalu.Maui.Navigation/) [![Nalu.Maui NuGet Package Downloads](https://img.shields.io/nuget/dt/Nalu.Maui.Navigation)](https://www.nuget.org/packages/Nalu.Maui.Navigation/)

The MVVM navigation service offers a straightforward and robust method for navigating between pages and passing parameters.
Expand Down
14 changes: 14 additions & 0 deletions Samples/Nalu.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,24 @@ public static MauiApp CreateMauiApp()

#if DEBUG
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
#endif

builder.Services.AddTransientPopup<CanLeavePopup, CanLeavePopupModel>();

builder.Services.AddKeyedSingleton<HttpClient>("dummyjson", (_, _) =>
{
#if IOS
var client = new HttpClient(new NSUrlBackgroundSessionHttpMessageHandler())
#else
var client = new HttpClient()
#endif
{
BaseAddress = new Uri("https://dummyjson.com/")
};
return client;
});

return builder.Build();
}
}
72 changes: 70 additions & 2 deletions Samples/Nalu.Maui.Sample/PageModels/OnePageModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Nalu.Maui.Sample.PageModels;

using System.Net.Http.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

Expand All @@ -8,14 +9,81 @@ public class AnimalModel
public string Name { get; set; } = null!;
}

public partial class OnePageModel(INavigationService navigationService) : ObservableObject
public partial class OnePageModel(
INavigationService navigationService,
[FromKeyedServices("dummyjson")] HttpClient httpClient) : ObservableObject
{
private static int _instanceCount;

public AnimalModel Animal { get; } = new AnimalModel { Name = "Dog" };
[ObservableProperty]
private string? _result1;

[ObservableProperty]
private string? _result2;

[ObservableProperty]
private string? _result3;

[ObservableProperty]
private string? _result4;

public AnimalModel Animal { get; } = new() { Name = "Dog" };

public int InstanceCount { get; } = Interlocked.Increment(ref _instanceCount);

[RelayCommand(AllowConcurrentExecutions = false)]
private Task PushThreeAsync() => navigationService.GoToAsync(Navigation.Relative().Push<ThreePageModel>());

[RelayCommand]
private async Task SendRequestAsync()
{
Result1 = null;
Result2 = null;
Result3 = null;
Result4 = null;

var parallelRequestTask1 = SendRequestAsync(CreateTestLongRequest(5000));
Result1 = "waiting...";
var parallelRequestTask2 = SendRequestAsync(CreateTestLongRequest(4000));
Result2 = "waiting...";

Result1 = await parallelRequestTask1;
Result2 = await parallelRequestTask2;

var followUpRequest = SendRequestAsync(CreateTestLongRequest(5000));
Result3 = "waiting...";
Result3 = await followUpRequest;

var followUpRequest2 = SendRequestAsync(CreateTestLongRequest(999999));
Result4 = "waiting...";
Result4 = await followUpRequest2;
}

private async Task<string> SendRequestAsync(HttpRequestMessage requestMessage)
{
string result;
try
{
var responseMessage = await httpClient.SendAsync(requestMessage);
result = await responseMessage.Content.ReadAsStringAsync();
}
catch (Exception e)
{
result = e.Message;
}

return result;
}

/// <summary>
/// Creates a request to an endpoint that will delay the response.
/// See https://dummyjson.com/docs#intro-test for more information.
/// </summary>
private HttpRequestMessage CreateTestLongRequest(int delayMs = 5000) =>
new()
{
RequestUri = new Uri($"test?delay={delayMs}", UriKind.Relative),
Method = HttpMethod.Post,
Content = JsonContent.Create(Animal),
};
}
40 changes: 40 additions & 0 deletions Samples/Nalu.Maui.Sample/Pages/OnePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,46 @@
Aspect="AspectFit"
Margin="0,16,0,24"/>

<Button Command="{Binding SendRequestCommand}"
Text="Send requests"
HorizontalOptions="Center" />

<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Parallel Request 1: " />
<Span Text="{Binding Result1}" />
</FormattedString>
</Label.FormattedText>
</Label>

<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Parallel Request 2: " />
<Span Text="{Binding Result2}" />
</FormattedString>
</Label.FormattedText>
</Label>

<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Follow-up Request 3: " />
<Span Text="{Binding Result3}" />
</FormattedString>
</Label.FormattedText>
</Label>

<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Erroring Request 4: " />
<Span Text="{Binding Result4}" />
</FormattedString>
</Label.FormattedText>
</Label>

<Label>
<Label.FormattedText>
<FormattedString>
Expand Down
5 changes: 5 additions & 0 deletions Samples/Nalu.Maui.Sample/Platforms/iOS/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
namespace Nalu.Maui.Sample;

using Foundation;
using UIKit;

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

[Export("application:handleEventsForBackgroundURLSession:completionHandler:")]
public virtual void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
=> NSUrlBackgroundSessionHttpMessageHandler.HandleEventsForBackgroundUrl(application, sessionIdentifier, completionHandler);
}
1 change: 0 additions & 1 deletion Source/Nalu.Maui.Core/Nalu.Maui.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

<ItemGroup>
<Folder Include="Platforms\Android\" />
<Folder Include="Platforms\iOS\" />
<Folder Include="Platforms\MacCatalyst\" />
</ItemGroup>
</Project>
Loading

0 comments on commit d3d7626

Please sign in to comment.