Vamos continuar a discutir as várias tarefas do Xamarin que temos que enfrentar regularmente, já que os artigos sobre esse assunto não aparecem com muita frequência. Este artigo será mais útil para desenvolvedores novatos, mas os mais experientes também podem ser interessantes.
Então, por que esse tópico? Tendo trabalhado em diferentes projetos, me peguei pensando que em cada um deles abordagens completamente diferentes são usadas para chamar serviços e tratar erros. Neste artigo, decidi reunir tudo o que tive que enfrentar, mostrar quais opções existem e pensar sobre os prós e os contras de cada um.
Vamos considerar diferentes abordagens com um exemplo simples, onde teremos uma solicitação de uma lista de modelos padrão do backend e, em seguida, convertê-los em uma lista de modelos de visualização para exibir a coleção. Não consideraremos a parte da interface do usuário aqui, nos restringiremos apenas ao trabalho dos serviços e ao modelo de visualização.
Portanto, nosso modelo simples, que solicitaremos do back-end:
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; }
}
E o modelo de visualização correspondente, no qual precisamos apenas de 2 campos do modelo para exibição subsequente:
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()
, , . , . , .
Se você também tem métodos interessantes que não são mencionados neste artigo - compartilhe-os nos comentários, talvez alguém aprenda algo útil para si mesmo.