Architectural Design for Mobile Applications: Part 2

  • Tutorial
To direct all the energy of the system in the necessary direction, you need to limit this system to the rules.


Hello, Habr! We continue a series of articles on the architectural design of mobile applications. Under cat, we’ll talk about designing UI layers. Welcome!

Evolving, various types of living organisms faced elementary tasks that needed to be solved to ensure their own survival.

Need to absorb energy from the environment? - solution: photosynthesis.
Need to ensure diversity in genes? - decision: gender division.
The more complex the body became, the more high-level tasks he had to solve.

As time went. Species appeared in which the priority of survival issues decreased significantly, and the resources of an individual were partially unused.
Evolution has taken a new vector. Most of the natural problems, whether it was food, protection from predators, protection from the weather, etc., were somehow solved, and there was nowhere to develop further, it seemed, but imagination played a role.

Further evolution no longer pursued the goal of ensuring survival, but the source of new tasks was consciousness, giving rise to development vectors such as “culture” and “science”. In fact, education and upbringing are not directly factors that ensure the survival of an individual; without them, it is quite possible to get by living in the forest.

Education and upbringing are not a natural goal set by nature; they were born by man himself.

The further development of the species is ensured by the goals that this species invents for itself.

And each of these goals is a limitation.

You can’t talk loudly in the library. You can not slurp at the table. You need to train a lot: writing, drawing, typing, playing the guitar - without difficulty in any way, and this is violence against oneself.

In order to develop, a person puts himself in the framework that he himself invents.

Do you want your software system to be at the peak of evolution?
Want your product to grow dynamically?
- Define the project with rigid frames and extension vectors.

The programming culture is that an engineer writes not in the way he wants, but in a way that complies with the rules.

Ps
And yes, any rule - be it a three-layer architectural design or a culture of holding cutlery - was invented by people, and therefore it is not objective in the last resort.

Nothing is true.
Everything is allowed.


What are you talking about?


Specific separation

In the previous article , which was mainly devoted to the architectural design of the service and transport layers of mobile applications (hereinafter referred to as the MP), a hint was given as to what prerequisites should be followed during the design of the UI level.

The following are theoretical considerations and practical conclusions, which, perhaps, will simplify your work, introducing a share of structuredness in the developed projects.

NB Please note that the ideology of building the user interface in high-quality products is strictly tied to the application platform, and to achieve some kind of “cross-platform” when choosing solutions is a stupid thing to say frankly.


You can’t just take it and equate systems originally designed to be different, even if Navigation Drawer can perform the same functions as UITabBar.

The current article focuses primarily on solutions applicable to the iOS platform.

Build UI


We continue to tell wonderful stories

The last time we ended up with the so-called user stories , which supposedly allow you to divide the project into logical parts so that later its source code can be easily maintained.

Suppose that the design of your application was not drawn by a crazy nerd, but by a more or less adequate artist, familiar with the concept of “UX”, and able to conveniently and organically fit all the controls into a chain of screens on a mobile device.



Using an example of a banking application, the interface is divided into typical stories , such as: “Greeting”, consisting of several slides representing the application; "Authorization" with fields of usernames, passwords, emails and others; finally, the main screen, which consists of several logical parts: “Home” (about your money), “Payments and transfers”, “Card” with ATMs and branches, and so on.

Each story includes several screens (or “pages”) that allow the user to perform certain actions.
“Payments and transfers”: go to the list of payments; go to the "Mobile Communications" section; choose an operator; enter the phone number and the amount of replenishment, choose the source of money - your credit card; to pay.

At the service level, there are all the necessary tools that can provide the UI with the necessary data and leverage on the back-end.

For separation into entities, I prefer to use not the classic MVC, but its MVP modification, which allows creating more abstract controllers, and scattering the code associated with the data model into classes that are directly “consumers” of this data.

For each user story, three key types of entities can be distinguished. These are View , ViewController and Helper .

ViewController- classes that act as controllers of individual logical units of the interface; As a rule, the application page acts as the logical unit, but with a more complex layout it can be useful to select child controllers on the page to offload the mother class.

View are classes directly represented on your storyboards: inheritors of UIView, UITableViewCell, UILabel, UIButton and so on.

Helper - utility classes that perform individual or delegated duties. These include utilities for formatting strings, classes that follow the UITableViewDataSource and UITableViewDelegate protocols, and so on.

Let's talk about the details.

ViewController


Learning from competitors

In general, iOS developers should learn a lot from their colleagues who make software for Android.

The latter to some extent inherited all the stiffness and architecture that is inherent in classical Java engineers, and therefore the architectural design of really cool Android applications looks much more harmonious than the architectural design of cool iOS applications.

The first limitation that Android colleagues introduced was the inability to transfer complex objects between controllers (activity) - for this, it is additionally necessary to ensure the serialization of these objects.

Yes, now everyone is already using fragments, and this whole story has sunk into oblivion, but the original message was correct: each controller should be as separate as possible.



Do not pass entire entities between controllers - pass their identifiers. Let each controller knock on the service level itself, receive detailed information from there on this entity (through its identifier) ​​- and in this way you will provide your application with a minimum of side effects inherent in imperative programming.

And, of course, the second limitation that Android developers should learn - they use the “Adapter” as the table delegate in the SDK - and this is a separate class, not a protocol, so merging things like view controller and UITableViewDelegate / UITableViewDataSource - simply impossible.

NB Clear text. UITableViewController is a direct violation of SOLID principles, and the engineer at Apple who invented this class made a serious mistake, because of which thousands of developers around the world now consider it normal to hang a dozen protocols on the view controller, such as UITableViewDataSource and UITableViewDelegate.


View


Abstracting and dividing into styles.

Above, I have already talked about using MVP, and now I will explain why.
So, there are two fragments responsible for filling the cell with data:

MVC:
RMREntity * entity = [self entityForIndexPath: indexPath];
cell.title = entity.name;
cell.subtitle = entity.shortDescription;

MVP:
RMREntity * entity = [self entityForIndexPath: indexPath];
[cell fillWithEntity: entity];

The second approach allows you to abstract from the structure of the UI, concentrating all the logic of the controllers and helper'ov around the processed data type - RMREntity.

Thus, behind the –fillWithEntity: interface, you can hide a dozen successor classes RMRTableViewCell, each of which will be able to render an entity in its own way. At the same time, for each UITableView it will use the same UITableViewDataSource, allowing you to significantly save on writing boilerplate code.

Total
First : do not forget: view can be inherited not only from SDK classes, but also from each other.

“The architectural design of the application should shout about what kind of application it is. If you see the blueprints of the library, you clearly understand that this is a library: it has reading rooms and bookshelves! ”

Do you have a Message data type in your application? Make an abstract MessageCell cell under it - and inherit other cells from it! Each class of your code must fulfill its function deterministically, and not allow logic, which should not be in it.

The second one . Remember, I mentioned a designer who is familiar with UX?

A good designer is a bit like a good programmer.
He has a set of typical templates that can be adapted to one or another need.
A good designer will first work out a color palette, and then will consistently apply it to render a project.

In addition to colors, a good designer will always have a “stylesheet” ready for each project:

Title: Helvetica Bold, 36pt.
Subtitle: Helvetica Medium, 24pt.
Amount of Money: Helvetica Light, 18pt, Light Blue.
Etc.

As an adequate developer, nothing prevents you from directly transferring this stylesheet to your code.

For example, you can create an abstract Label class that, at any initialization, will query the factory method –fontStyle that returns the font style — and apply this style.

Heirs, in turn, will simply return the necessary writing style. Thus, you will have classes at hand: HeaderLabel, SubheaderLabel, MoneyAmountLabel ... and, lo and behold! They can simply be substituted into the "Class" field directly inside the Interface Builder'a applicable to the created layout.

Helper


Tested design

It has already been said about those things that controllers should not do.
So who should? - answer: utilities.

The most important thing to keep in mind and what to observe is a simple rule: utilitarian classes should not carry any information. They should not have public properties, and all their logic should be “pass-through” with a minimum of void methods, as if we were writing in a functional style: a method should always return something.

Due to this “lack of state” of the utility, it is quite safe to use application layers on different layers of the application logic, however, do not be lazy to create separate assistants for controllers, representations, business logic and other things.

Utility classes - or heplers - are the first candidates for the role of test entities.

Formatting dates, sorting arrays, creating images from other images - all this is an algorithm that easily falls within the sequence “arrange, act, assert”, and may well be sacrificed to automatic tests.

Conclusion


Summary

In this article I did not want to give anyone instructions .
Each has its own head on its shoulders, and each is able to draw its own conclusions. Or do not draw conclusions at all.

Of course, one could write more specifics: what the project structure should look like, where to put files and directories, where resources should lie.
In what sequence to implement methods in classes, how to name properties.

However, I did not consciously do this: the material is intentionally simple, as the source code does not have to be complicated .

Stay with us.

Also popular now: