Let's continue to discuss various tasks for Xamarin that we have to face regularly, since articles on this topic do not appear very often. This article will be more useful for novice developers, but more experienced ones can also be interesting.
So why such a topic? Having worked on different projects, I caught myself thinking that on each of them completely different approaches are used to call services and handle errors. In this article, I decided to collect everything that I had to face, show what options are there, and think about what pros and cons each have.
Let's consider different approaches with a simple example, where we will have a request for a list of standard models from the backend, and then convert them to a list of view-models to display the collection. We will not consider the UI part here, we will limit ourselves only to the operation of services and the view model.
So, our simple model, which we will request from the 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; }
}
And the corresponding View Model, in which we need only 2 fields from the model for subsequent display:
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()
, , . , . , .
If you also have interesting methods that are not mentioned in this article - share them in the comments, perhaps someone will learn something useful for themselves.