Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/content/samples/todoapp/maui.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ Before you begin adjusting the application for offline usage, you must [deploy a

## Update the application for datasync operations

All the changes are isolated to the `Database/AppDbContext.cs` file.
All the changes are isolated to the `Models/AppDbContext.cs` file. You can change the definition of `OFFLINE_SYNC_ENABLED` at the top of the file to make all the changes.

```csharp
#define OFFLINE_SYNC_ENABLED
```

The following changes are made to the database context:

1. Change the definition of the class so that it inherits from `OfflineDbContext`:

Expand Down
41 changes: 24 additions & 17 deletions samples/todoapp/TodoApp.MAUI/MainPage.xaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:TodoApp.MAUI.Models"
xmlns:viewmodels="clr-namespace:TodoApp.MAUI.ViewModels"
x:DataType="viewmodels:MainViewModel"
x:Class="TodoApp.MAUI.MainPage"
Title="TodoApp">
<ContentPage
x:Class="TodoApp.MAUI.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:TodoApp.MAUI.Models"
xmlns:viewmodels="clr-namespace:TodoApp.MAUI.ViewModels"
Title="TodoApp"
x:DataType="viewmodels:MainViewModel">
<NavigationPage.TitleView>
<StackLayout Style="{StaticResource titleViewContainer}">
<Label Style="{StaticResource titleViewLabel}">TodoApp</Label>
<ImageButton Style="{StaticResource titleViewRefreshIcon}" Command="{Binding RefreshItemsCommand}" />
<ImageButton Command="{Binding RefreshItemsCommand}" Style="{StaticResource titleViewRefreshIcon}" />
</StackLayout>
</NavigationPage.TitleView>

<Grid AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="Azure">
<Grid
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Azure">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
Expand All @@ -22,8 +26,8 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<!-- List of items -->
<ListView ItemsSource="{Binding Items}" ItemTapped="OnListItemTapped">
<!-- List of items -->
<ListView ItemTapped="OnListItemTapped" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:TodoItem">
<ViewCell>
Expand All @@ -37,23 +41,26 @@
</Grid.ColumnDefinitions>

<Label Style="{StaticResource listItemTitle}" Text="{Binding Title}" />
<Image Grid.Column="1" IsVisible="{Binding IsComplete}" Style="{StaticResource listItemIcon}" />
<Image
Grid.Column="1"
IsVisible="{Binding IsComplete}"
Style="{StaticResource listItemIcon}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<!-- Entry box -->
<!-- Entry box -->
<Frame Grid.Row="1" Style="{StaticResource roundedCornerFrame}">
<HorizontalStackLayout>
<Image Style="{StaticResource addItemIcon}" />
<Entry
x:Name="addItemEntry"
Style="{StaticResource addItemEntry}"
<Entry
x:Name="addItemEntry"
Placeholder="Enter Todo Item Text"
ReturnCommand="{Binding AddItemCommand}"
ReturnCommandParameter="{Binding Source={x:Reference addItemEntry}}" />
ReturnCommandParameter="{Binding Text, Source={x:Reference addItemEntry}}"
Style="{StaticResource addItemEntry}" />
</HorizontalStackLayout>
</Frame>
</Grid>
Expand Down
4 changes: 2 additions & 2 deletions samples/todoapp/TodoApp.MAUI/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ public MainPage()
protected override void OnAppearing()
{
base.OnAppearing();
this._viewModel.RefreshItemsCommand.Execute();
this._viewModel.RefreshItemsCommand.Execute(null);
}

public void OnListItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item is TodoItem item)
{
this._viewModel.UpdateItemCommand.Execute(item);
this._viewModel.UpdateItemCommand.Execute(item.Id);
}

if (sender is ListView itemList)
Expand Down
53 changes: 33 additions & 20 deletions samples/todoapp/TodoApp.MAUI/Models/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,50 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#undef OFFLINE_SYNC_ENABLED

using CommunityToolkit.Datasync.Client.Http;
using CommunityToolkit.Datasync.Client.Offline;
using Microsoft.EntityFrameworkCore;
using TodoApp.MAUI.Services;

namespace TodoApp.MAUI.Models;

#if OFFLINE_SYNC_ENABLED
public class AppDbContext(DbContextOptions<AppDbContext> options) : OfflineDbContext(options)
#else
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
#endif
{
public DbSet<TodoItem> TodoItems => Set<TodoItem>();

//protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder optionsBuilder)
//{
// HttpClientOptions clientOptions = new()
// {
// Endpoint = new Uri("https://YOURSITEHERE.azurewebsites.net/"),
// HttpPipeline = [new LoggingHandler()]
// };
// _ = optionsBuilder.UseHttpClientOptions(clientOptions);
//}
#if OFFLINE_SYNC_ENABLED
protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder optionsBuilder)
{
HttpClientOptions clientOptions = new()
{
Endpoint = new Uri("https://YOUR_SITE_HERE.azurewebsites.net/"),
HttpPipeline = [new LoggingHandler()]
};
_ = optionsBuilder.UseHttpClientOptions(clientOptions);
}
#endif

public async Task SynchronizeAsync(CancellationToken cancellationToken = default)
{
//PushResult pushResult = await this.PushAsync(cancellationToken);
//if (!pushResult.IsSuccessful)
//{
// throw new ApplicationException($"Push failed: {pushResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
//}

//PullResult pullResult = await this.PullAsync(cancellationToken);
//if (!pullResult.IsSuccessful)
//{
// throw new ApplicationException($"Pull failed: {pullResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
//}
#if OFFLINE_SYNC_ENABLED
PushResult pushResult = await this.PushAsync(cancellationToken);
if (!pushResult.IsSuccessful)
{
throw new ApplicationException($"Push failed: {pushResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
}

PullResult pullResult = await this.PullAsync(cancellationToken);
if (!pullResult.IsSuccessful)
{
throw new ApplicationException($"Pull failed: {pullResult.FailedRequests.FirstOrDefault().Value.ReasonPhrase}");
}
#endif
}
}

Expand Down
43 changes: 43 additions & 0 deletions samples/todoapp/TodoApp.MAUI/Services/LoggingHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;

namespace TodoApp.MAUI.Services;

/// <summary>
/// A delegating handler that logs the request/response to stdout.
/// </summary>
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler() : base()
{
}

public LoggingHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine($"[HTTP] >>> {request.Method} {request.RequestUri}");
await WriteContentAsync(request.Content, cancellationToken);

HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

Debug.WriteLine($"[HTTP] <<< {response.StatusCode} {response.ReasonPhrase}");
await WriteContentAsync(response.Content, cancellationToken);

return response;
}

private static async Task WriteContentAsync(HttpContent? content, CancellationToken cancellationToken = default)
{
if (content != null)
{
Debug.WriteLine($"[HTTP] >>> {await content.ReadAsStringAsync(cancellationToken)}");
}
}
}

6 changes: 5 additions & 1 deletion samples/todoapp/TodoApp.MAUI/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@

using CommunityToolkit.Datasync.Client;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using TodoApp.MAUI.Models;
using TodoApp.MAUI.Services;

namespace TodoApp.MAUI.ViewModels;

public class MainViewModel(AppDbContext context, IAlertService alertService) : ObservableRecipient
public partial class MainViewModel(AppDbContext context, IAlertService alertService) : ObservableRecipient
{
[ObservableProperty]
private bool _isRefreshing = false;

[ObservableProperty]
private ConcurrentObservableCollection<TodoItem> items = [];

[RelayCommand]
public async Task RefreshItemsAsync(CancellationToken cancellationToken = default)
{
if (IsRefreshing)
Expand All @@ -41,6 +43,7 @@ public async Task RefreshItemsAsync(CancellationToken cancellationToken = defaul
}
}

[RelayCommand]
public async Task UpdateItemAsync(string itemId, CancellationToken cancellationToken = default)
{
try
Expand All @@ -60,6 +63,7 @@ public async Task UpdateItemAsync(string itemId, CancellationToken cancellationT
}
}

[RelayCommand]
public async Task AddItemAsync(string text, CancellationToken cancellationToken = default)
{
try
Expand Down