📜 ⬆️ ⬇️

Mobile application with automatic generation of forms: our case

Mobile applications are not always simple and concise, as we like - the developers. Other applications are created to solve complex user problems and contain many screens and scenarios. For example, applications for testing, questionnaires and surveys - wherever in the process you need to fill out many forms. About this application and will be discussed in this article.



We began to develop a mobile application for agents who are engaged in field registration of insurance policies. They fill out large forms in the application with customer data: information about the car, owners, drivers, etc. Although each form has its own sections, cells and structure, and each item of the questionnaire requires a unique data type (row, date, sub-document), the screen forms were quite similar. But the main thing is their number ... Nobody wants to repeat the visualization and processing of the same type elements many times.
')
To avoid many hours of manual work on creating forms, you need to use a little quick wits and a lot of dynamic UI construction. In this article, we want to share a way to solve this problem.

For an elegant solution of the problem, we used the object generation mechanism - ViewModels, which are used to build custom forms using tables.



In normal operation, for each individual table that the developer wants to see on the screen, its own ViewModel class should be created. It determines the visual component of the table. We decided to go up one level and generate the ViewModels and Models themselves dynamically using a simple structure description via the Enum fields.

How it works


It all started with enum'a. For each questionnaire we create a unique enum - these are our sections of the questionnaire. One of its methods is to return the cell array of this section.

The cells of the table will also be enum with additional functions that will describe the properties of the cells. In such functions we set the name of the cell, the initial value. Later added options such as


We subscribe all sections to the common QuestionnaireSectionCellType protocol in order to eliminate binding to a specific section, we will do the same with all table cells (QuestionnaireCellType).

protocol QuestionnaireSectionCellType { var title: String { get } var sectionCellTypes: [QuestionnaireCellType] { get } } protocol QuestionnaireCellType { var title: String { get } var initialValue: Any? { get } var isHidden: Bool { get } var parentFields: [QuestionnaireCellType] { get } … } 

Such a model will be very easy to fill. We simply run through all sections, in each section we run through an array of cells and add them to the model array.

On the example of the policyholder's screen (enum with sections - InsurantSectionType):

 final class InsurantModel: BaseModel<QuestionnaireCellType> { override init() { super.init() initParameters() } private func initParameters() { InsurantSectionType.allCases.forEach { type in type.sectionCellTypes.forEach { if let valueModel = ValueModel(type: $0, parentFields: $0.parentFields, value: $0.initialValue) { valueModels.append(valueModel) } } } } } 

Done! Now we have a table with initial values. Add methods for reading the value by the QuestionnaireCellType key and for saving to the required element of the array.

Some models may have optional fields, so we added an array with optional keys. When validating a model, these keys may not contain values, but the model will be considered full.

Further, for convenience, all the values ​​in the ValueModel are signed by the StringRepresentable protocol to the general protocol in order to limit the list of possible values ​​and add a method to display the value in the cell.

 protocol StringRepresentable { var stringValue: String? { get } } 

The functionality grew, and many other properties and methods appeared in the models: cleaning up the model (some models need to set initial values), support for a dynamic array of values ​​(value: Array), etc.

This approach was very convenient for storage in the database using Realm. To fill in the questionnaire, you can choose a previously saved filled model. To extend the CTP policy, the agent will no longer need to fill in the user's documents, the drivers attached to them, and the data on the PTS for the new one. Instead, you can simply reuse to fill the existing one.

To modify or supplement the tables, you only need to find the ViewModel related to a specific screen, find the required enum, which is responsible for displaying the required block, and add or correct several case'ov. Everything, the table will take the necessary form!

Filling out the form with test values ​​was also very convenient and fast. This way you can quickly generate any test data. And if you add a separate file with initial data, from where the program will take the value into each specific field of the questionnaire, then even a beginner will be able to generate ready-made questionnaires, without delving into and disassembling the rest of the code, except for a specific file.

Dependencies


A separate task that we solved during the development process is handling dependencies. Some elements of the questionnaire were related. Thus, the document number cannot be filled without choosing the type of this document, the house number cannot be specified without specifying the city and street, etc.



We made an update of the questionnaire values ​​with clearing all dependent fields (For example, removing or changing the document type, we clear the “document number” field):

 func updateValueModel(value: StringRepresentable?, for type: QuestionnaireCellType) { guard let model = valueModels.first(where: { $0.type.equal(to: type) }) else { return } model.value = value clearRelativeValues(type: type) } func clearRelativeValues(type: QuestionnaireCellType) { _ = valueModels.filter { $0.parentFields.contains(where: { $0.equal(to: type) }) } .compactMap { $0.type } .compactMap { updateValueModel(value: nil, for: $0) } } 

The pitfalls that had to be solved during the development, and how we managed


It is clear that this method is convenient for screens with the same functionality (filling in fields), but not so convenient if unique elements or functions appear on one separate screen that are not on other screens. In our application, these were:


Conclusion


This experience allowed us to quickly implement a mobile application that is easy to maintain. Versatility allows you to quickly generate tables with different types of input data: we made 20 windows in just a week. This approach also speeds up the process of testing the application. In the near future we will reuse the finished factory for the rapid generation of new tables and new functionality.

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


All Articles