Continuons à discuter des différentes tâches pour Xamarin auxquelles nous devons faire face régulièrement, car les articles sur ce sujet n'apparaissent pas très souvent. Cet article sera plus utile pour les développeurs novices, mais les plus expérimentés peuvent aussi être intéressants.
Alors pourquoi un tel sujet? Ayant travaillé sur différents projets, je me suis surpris à penser que sur chacun d'eux des approches complètement différentes sont utilisées pour appeler les services et gérer les erreurs. Dans cet article, j'ai décidé de rassembler tout ce que j'avais à affronter, de montrer les options disponibles et de réfléchir aux avantages et aux inconvénients de chacun.
Considérons différentes approches avec un exemple simple, où nous aurons une requête pour une liste de modèles standard du backend, puis les convertirons en une liste de modèles de vue pour afficher la collection. Nous ne considérerons pas ici la partie UI, nous nous limiterons uniquement au travail des services et au modèle de vue.
Donc, notre modèle simple, que nous demanderons au backend:
public class ItemModel
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string CreatedDate { get; set; }
public string ModifiedDate { get; set; }
}
Et le modèle de vue correspondant, dans lequel nous n'avons besoin que de 2 champs du modèle pour un affichage ultérieur:
public class ItemViewModel : ViewModel
{
public ItemViewModel(ItemModel item)
{
Title = item.Title;
Description = item.Description;
}
public string Title { get; }
public string Description { get; }
}
, -:
public interface IDataService
{
Task<IEnumerable<ItemModel>> LoadItemsAsync();
}
RequestService, :
public interface IRequestService
{
Task<T> GetAsync<T>(string url);
}
- ItemViewModel. - ObservableCollection MvvmCross, AddRange()
, UI.
public class MainViewModel : ViewModel
{
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
}
public MvxObservableCollection<ItemViewModel> Items { get; set; } = new();
}
- MainViewModel . -.
, :
public async Task Initialize()
{
var result = await _dataService.LoadItemsAsync();
var itemModels = result.ToList();
if (itemModels.Any())
{
var items = new List<ItemViewModel>();
itemModels.ForEach(m => items.Add(new ItemViewModel(m)));
Items.AddRange(items);
}
}
LoadItemsAsync()
- :
public Task<IEnumerable<ItemModel>> LoadItemsAsync()
{
var url = "https://customapiservice/v1/items";
return _requestService.GetAsync<IEnumerable<ItemModel>>(url);
}
, , , - 400- 500- . , crash. , , :
public async Task Initialize()
{
try
{
var result = await _dataService.LoadItemsAsync();
var itemModels = result.ToList();
if (itemModels.Any())
{
var items = new List<ItemViewModel>();
itemModels.ForEach(m => items.Add(new ItemViewModel(m)));
Items.AddRange(items);
}
}
catch (Exception e)
{
//
}
}
, . .
:
, , -
, ,
:
try-catch, -
, -
, - try-catch, . LoadItemsAsync()
:
public async Task<IEnumerable<ItemModel>> LoadItemsAsync()
{
IEnumerable<ItemModel> result;
try
{
var url = "https://customapiservice/v1/items";
result = await _requestService.GetAsync<IEnumerable<ItemModel>>(url);
}
catch (Exception e)
{
result = new List<ItemModel>();
}
return result;
}
- , , - . , - . - null, , . , api null, .
:
- , breakpoints
try-catch, -
:
try-catch
, exception,
. , :
public class ServiceResult<TResult, TError>
{
public ServiceResult(TResult result)
{
IsSuccessful = true;
Result = result;
}
public ServiceResult(TError error)
{
IsSuccessful = false;
Error = error;
}
public bool IsSuccessful { get; }
public TResult Result { get; }
public TError Error { get; }
}
LoadItemsAsync()
, :
public interface IDataService
{
Task<ServiceResult<IEnumerable<ItemModel>, Exception>> LoadItemsAsync();
}
public async Task<ServiceResult<IEnumerable<ItemModel>, Exception>> LoadItemsAsync()
{
try
{
var url = "https://customapiservice/v1/items";
var result = await _requestService.GetAsync<IEnumerable<ItemModel>>(url);
return new ServiceResult<IEnumerable<ItemModel>, Exception>(result);
}
catch (Exception e)
{
return new ServiceResult<IEnumerable<ItemModel>, Exception>(e);
}
}
, - , - , try-catch:
public async Task Initialize()
{
var result = await _dataService.LoadItemsAsync();
if (result.IsSuccessful)
{
var itemModels = result.Result.ToList();
if (itemModels.Any())
{
var items = new List<ItemViewModel>();
itemModels.ForEach(m => items.Add(new ItemViewModel(m)));
Items.AddRange(items);
}
}
else
{
//
}
}
:
, .
try-catch
- , , -
:
, , ServiceResult
-, - try-catch, ,
try-catch
OperationFactory FlexiMvvm. , Func OnSuccess OnError, , . DataService
:
public interface IDataService
{
Task LoadItemsAsync(
Func<IEnumerable<ItemModel>, Task> onSuccess = null,
Func<Exception, Task> onError = null);
}
public async Task LoadItemsAsync(
Func<IEnumerable<ItemModel>, Task> onSuccess = null,
Func<Exception, Task> onError = null)
{
try
{
var url = "https://customapiservice/v1/items";
var result = await _requestService.GetAsync<IEnumerable<ItemModel>>(url);
onSuccess?.Invoke(result);
}
catch (Exception e)
{
onError?.Invoke(e);
}
}
- :
public async Task Initialize()
{
await _dataService.LoadItemsAsync(HandleLoadSuccess, HandleLoadError);
}
private Task HandleLoadSuccess(IEnumerable<ItemModel> result)
{
var itemModels = result.ToList();
if (itemModels.Any())
{
var items = new List<ItemViewModel>();
itemModels.ForEach(m => items.Add(new ItemViewModel(m)));
Items.AddRange(items);
}
return Task.CompletedTask;
}
private Task HandleLoadError(Exception arg)
{
//
}
, - , Initialize()
, , .
, :
-
:
- OnSuccess OnError, , ,
try-catch
, OperationFactory. ServiceResult
. ServiceCall
, . 3 Func. - , - , - .
ExecuteAsync()
try-catch-finally. exception - ErrorHandler, try-catch, exception. - SuccessHandler, try-catch.
public class ServiceCall<TResult>
{
private readonly Func<Task<TResult>> _callAction;
public ServiceCall(Func<Task<TResult>> callAction)
{
_callAction = callAction;
}
public Func<TResult, Task> SuccessHandler { get; set; }
public Func<Exception, Task> ErrorHandler { get; set; }
public async Task ExecuteAsync()
{
TResult result = default;
var isSuccess = false;
try
{
result = await _callAction.Invoke();
isSuccess = true;
}
catch (Exception e)
{
try
{
await ErrorHandler.Invoke(e);
}
catch (Exception)
{
}
}
finally
{
if (isSuccess)
{
try
{
await SuccessHandler.Invoke(result);
}
catch (Exception)
{
}
}
}
}
}
ServiceCallHandler
, , . , .
public interface IServiceCallHandler<TResult>
{
IServiceCallHandler<TResult> OnSuccessAsync(Func<TResult, Task> handler);
IServiceCallHandler<TResult> OnErrorAsync(Func<Exception, Task> handler);
Task ExecuteAsync();
}
public class ServiceCallHandler<TResult> : IServiceCallHandler<TResult>
{
private ServiceCall<TResult> _serviceCall;
public ServiceCallHandler(ServiceCall<TResult> serviceCall)
{
_serviceCall = serviceCall;
}
public IServiceCallHandler<TResult> OnSuccessAsync(Func<TResult, Task> handler)
{
_serviceCall.SuccessHandler = handler;
return this;
}
public IServiceCallHandler<TResult> OnErrorAsync(Func<Exception, Task> handler)
{
_serviceCall.ErrorHandler = handler;
return this;
}
public Task ExecuteAsync() => _serviceCall.ExecuteAsync();
}
public interface IDataService
{
IServiceCallHandler<IEnumerable<ItemModel>> LoadItems();
}
public IServiceCallHandler<IEnumerable<ItemModel>> LoadItems()
{
var serviceCall = new ServiceCall<IEnumerable<ItemModel>>(LoadItemsAction);
return new ServiceCallHandler<IEnumerable<ItemModel>>(serviceCall);
}
private Task<IEnumerable<ItemModel>> LoadItemsAction()
{
var url = "https://customapiservice/v1/items";
return _requestService.GetAsync<IEnumerable<ItemModel>>(url);
}
Initialize()
- , HandleLoadSuccess
HandleLoadError
.
public async Task Initialize()
{
await _dataService
.LoadItems()
.OnSuccessAsync(HandleLoadSuccess)
.OnErrorAsync(HandleLoadError)
.ExecuteAsync();
}
:
, , , . OnSuccess OnError .
try-catch , -. ServiceCall.
, , :
, , IServiceCallHandler
ExecuteAsync()
, , . , . , .
Si vous avez également des méthodes intéressantes qui ne sont pas mentionnées dans cet article - partagez-les dans les commentaires, peut-être que quelqu'un apprendra quelque chose d'utile pour lui-même.