📜 ⬆️ ⬇️

Self-sufficient controllers on Xamarin.Forms: "Re-use the code to the maximum!". Part 2



So, in the last chapter I considered such an architectural model as control - service. In such a model, a service is created on the business logic layer, which has events that the ViewModel of the desired controller subscribes to. As a result of this approach, the interface mapping layer does not need to think about the data, but only deal with interface things.

The last chapter gave an example of authorization. Initially, it is not clear why using the control - service model, however, if self-sufficient controllers should be more - turning one control to another can be problematic if you directly access the ViewModel methods of another control, because of possible loops of method calls - subscriptions.

Consider the following example: Product Catalog - The basket to which the product is added. Suppose a situation in which you need to display a catalog of products, in the form of a listing, each product can be added to the basket, while the product added to the basket is marked in the listing as added. There is a double dependence.
')


In such a model, the controllers are strongly dependent on each other, which immediately entails a number of problems. The main one is that it cannot be used in another application, for example, a listing control without a basket, and reuse is our main goal. In this situation, the model of the controller - the service is more suitable. Let ViewModel continue to display all relevant information, but the question of interaction with other controllers will carry it to the service level. The services themselves are singletones, their interfaces are added to the DI container. Thus, for example, when adding a product to the cart, it will be enough to check once if the DI container has a cart service and, if so, to display the add to cart button, and also subscribe to the changes in the cart itself. The same with the basket service. Products can be added from anywhere, for example from the site, and in the application only display the cart. The cart in this case does not depend on the availability of the listing. And the controller itself can now be reused in all other applications within the company.

Let's go specifically to the code. Suppose there is an API with methods:


It is necessary, based on existing methods, to implement an application with a listing of products, the listing header will contain a basket icon, with the current number of products, when clicked, the basket screen opens. On the screen with the basket, you can increase or decrease the quantity of a specific product in the basket or completely clear it.

Before creating a listing control, I would like to touch on the topic of the most appeal to API methods. When creating a client application, you should always take into account that not all requests can reach the server (at some point in time, the Internet may simply disappear). Therefore, it is necessary to implement the data access layer, which, taking into account this situation, will first be able to stand in a queue of requests, and, when the Internet appears, make the request again. In such a situation, in order not to slow down the client's work with the application, it is possible, depending on the required accuracy of the data, to display data from the cache. This topic deserves a separate series of articles. How we solve this situation in Mobile Dimension, if there is such an interest - we will describe in a separate series of articles.

My colleague in this series of articles tells how to create the interface of these controllers, how to expand existing ones, or how to make adaptive controls to any screen size.

The business logic layer does not need to know how data comes in, but considering that all services are at the business logic level - they will access the “virtual” data layer, which can return a result upon request (successful or not). successful). It should be borne in mind that a request can be processed an indefinite amount of time, however, as my colleague wrote in her article on interfaces, the interface should immediately respond to any press. Therefore, any query at the data level (DAL) has events that you can subscribe to - this is the event of a successful query with a result, and an unsuccessful query with errors. In general, this is a ticket (tiket) in which there is the request itself, the method of accessing the API, and the events themselves. Thus, the service sends a request, subscribes to these events, and, depending on the result that is received, causes its own events to which any controller subscribes. In the controller itself, by pressing the start of work is initiated by the request in the service (BLL), at this moment the loading indicator is shown and the result event is subscribed.

For example, the call request code of adding goods to the cart, at the service level, will look like this:

public event Action<string, string> OnProductAddedSuccessfully; public event Action<string> OnProductAddedFailure; public void StartAddingProduct(string sku) { var newProduct = new BasketProduct() { Sku = sku, State = Enums.RequestState.InProgress }; //        _products.Add(newProduct); //   ,  ,    ... var tiket = _basketRepository.AddToBasket(sku); tiket.OnSuccess += (response) => { //  ,           if(response.Data.Succseeded != null && response.Data.Succseeded.Value) { //        ,      (positionId) newProduct.PositionId = response.Data.PositionId; newProduct.State = Enums.RequestState.Succseeded; //  (),     OnProductAddedSuccessfully?.Invoke(newProduct.Sku, newProduct.PositionId); } else { // ,      OnProductAddedFailure?.Invoke(sku); newProduct.State = Enums.RequestState.Failed; } }; //   ,    (     ) var priceTicket = _catalogRepository.GetPriceTicket(sku); priceTicket.OnSuccess += (response) => { if(response.Data != null){ //    newProduct.Price = response.Data.Price; } }; } 

Service method of adding goods to the cart

As you can see from the example, we have 2 events that indicate what they were trying to add to the basket. The code for removing goods from the service will look about the same.

The events of this basket service can now be subscribed to as a ViewModel basket, as well as a listing. The product from the listing is added directly to the service, and since both the listing and the basket are subscribed to the service events, the necessary changes will occur in all views. The implementation of a subscription to changes will occur as follows:

 public int? TotalCount { get { return _basketService.TotalCount; } } public int? TotalPrice { get { return _basketService.TotalPrice; } } void _basketService_OnProductAddedSuccessfully(string sku, string positionId) { var product = Products.ToList().FirstOrDefault(x => x.Sku == sku); product.CountInBasket++; product.IsAddingInProfress = false; product.PositionIds.Add(positionId); //     ,     ,     -  RaizePropertyChanged(nameof(TotalCount), nameof(TotalPrice)); } 

Service method of adding goods to the cart

The fields from the ViewModel displayed by the interface do not look as usual, the code has become less and the update of the fields has become manageable. This is an added bonus of this approach. Other ViewModel can also exist there, if you take their interaction to the service level - the code becomes clearer, it is easier to maintain.



The disadvantage of this approach is the moment of debag itself. If suddenly, somewhere at the service or data level, NRE or other exceptions occur, the error itself will be displayed at the ViewModel level. Visual Studio will not determine at what level exactly the error occurred, since in fact the error occurs in the callback function. Perhaps this can be configured somewhere in the studio itself, but the call stack usually saves me, where you can see where exactly the error came from.

imageimage

On the images you can see how the dynamic loading of goods from the service on the catalog page works. At the time of adding the product to the cart, its quantity in the stock decreases, even if you add it from the cart service that is not related to the leftovers. All synchronization works thanks to the ViewModels subscription to the corresponding services in the changes.

An example can be found on GitHub .

In this article, we looked at how we organize the interaction of two self-sufficient controllers, which can later be used in other projects. In the next, final chapter, I will talk about how to collect all the created controls in the NuGet libraries and reuse them in other projects. I will also consider the problem of creating a custom interface for various platforms.

Source: https://habr.com/ru/post/331196/


All Articles