Many developers are faced with the task of creating customized applications. For example, developing several versions of the same application or changing the standard application to the requirements of the customer. We at Rambler & Co faced this challenge when developing Rambler box office and its branded versions for individual cinemas. This article will look at the evolution of the architecture of such an application, as well as the tools that simplify our lives.

Formulation of the problem
It is necessary to invent and prepare a base for the quick creation of typical cash register applications. Each cinema wants separate applications, the application is constantly evolving, bugs are being fixed and new features are being added, which also need to be pulled into the branded versions.
Goals and Conversion
Custom applications have a positive effect on conversion. The main goal of users in the application is to view the schedule of your favorite cinema and purchase tickets. The presence of applications allows you to reduce the number of clicks from opening to pressing a button to buy a ticket by 2-3 clicks. And as you know, every click is -N% of users. There is also the opportunity to convey to users information about promotions and discounts.
')
Architecture
Changes relate to the UI, the basic entity and functionality remain unchanged. Filtering sources is not required, is performed by the API.
Initially, the task was to make one branded version, development plans were very vague. The branch by version was via if. Like that:
if (TYPE == KASSA1) { return 1; } else if (TYPE == KASSA2) { return 2; } else { return 3; }
Or via switch:
switch (TYPE) { case KASSA1: return 1; case KASSA2: return 2; default: return 3; }
The solution is very bad, many flaws and almost no advantages. All development in one code base, a huge amount of code, complex debugging and debugging, deployment problem. The whole range of emotions in one picture:
Flavors
The next branch of evolution was the use of Gradle Flavors + build Types.
Separate versions of applications are created, switching between them occurs in the build types window, configurations can be set in parallel, for example: debug, release, manager, testOrder, and so on. Each configuration contains its own code and resources. One of the unpleasant flavors features is complicated code navigation in an inactive branch. Configuration example:
productFlavors { kassaCinema1 { applicationId "ru.rambler.cinema1" versionName "1.3" } kassaCinema2 { applicationId "ru.rambler.cinema2" versionName "1.1" } }
Key features:
- - Selected resources
- - Separate code
- - Easy assembly
Disadvantages:
- —No possibility of checking the correctness of other branches
- The complexity of navigation with a large number of branches.
More details can be found:
tools.android.com/tech-docs/new-build-system/user-guide and here:
developer.android.com/tools/building/configuring-gradle.htmlFor most applications, this functionality should suffice, but because of the possibility of active work in only one branch, as well as inconvenience with a large number of versions, I had to abandon this system in favor of the SDK or android library.
"SDK or application - library"
The next step was to transfer the main code to the SDK. The main application is created as a library, i.e. in the build gradle we change:
apply plugin: 'com.android.application'
on
apply plugin: 'com.android.library'
and remove all the excess from the manifest.
It also creates the main implementation, it describes the manifest and connects the library, its code or resources in the main version are not used.
All the logic and the main code is in the library, in the branded versions there is only the design, in rare cases, the individual blocks of code.
Implementation
All the logic of branded applications is located in the main factory. She is responsible for fine-tuning the entire application. Initialization in Application.
public class KassaApp extends Application { private MainFactory mainFactory; @Override public void onCreate() { super.onCreate(); mainFactory = createMainFactory() } protected MainFactory createMainFactory() { return new MainFactory(); } }
Example Application in a custom application:
public class CustomApp extends KassaApp { @Override protected MainFactory createMainFactory() { return new CustomFactory(); } }
Sample basic entity factory:
public class MainFactory { public FragmentManager createFragmentManager() { return new FragmentManager(); } public UIManager createUIManager() { return new UIManager(); } public String getLatLng() { return LocationSettingsManager.getInstance().getLatLngParams(); } public String getCustomUrl() { return getString(R.string.custom_url); } public Place getCustomPlace() { throw new IllegalStateException(getString(R.string.error_illegal)) } }
Change view through managers
public class UIManager { public boolean hasHeaderLocation() { return getBoolean(R.bool.config_header_location_enabled); } public boolean hasPosterSearch() { return getBoolean(R.bool.config_poster_search_enabled); } }
Usage example:
if (!uiManager.hasHeaderLocation()) { disableHeaderLocationView(); }
FragmentManager is responsible for working with fragments.
public class FragmentManager { public Fragment getOneCinemaFragment() { throw new IllegalStateException(getString(R.string.error_illegal)); } public Fragment getNavNowFragment() { return new NavNowFragment(); } public Fragment getNavSupportFragment() { return new NavSupportFragment(); } }
When adding elements specific to all typical movie theaters, we add them to the main project.
When adding atypical elements, we use the substitution of fragments
Webview
In case the customer wants to be able to dynamically change information in the application, we add a webView with embedded links. A separate fragment is responsible for working with webView, simply replacing the link.
<string name="custom_url_news">http://cinema1.ru/news/</string>
Content and page layout remains for the customer.
An example of a menu with 4 elements is webView (movie bars, advertising, room rental, promotions). Design
The design in this process is one of the most labor-intensive moments, by agreement the design is drawn according to the example of the main version, one-in-one. All elements are delivered in the same format, with the same file names, the same arrangement of elements. As a result, you just need to copy the res-drawable directory. The same situation with colors and fonts.
<resources> <color name="kassa_main_color">#1aa0f0</color> <color name="kassa_delimiter">#bbe2f9</color> <color name="kassa_clickable">#E3E3E3</color> <color name="action_color_active">#1aa0f0</color> <color name="action_color_pressed">#bbe2f9</color> </resources>
All information on the cinema is stored in the resources:
<string name="support_custom_email ">cinema1@cinema1.ru</string> <string name="app_name">Cinema 1</string> <string name="support_custom_phone">+7 495 777 77 77</string>
Similarly stored information about the visibility of elements, design and so on.
An example of changing an application with only resources Application wizard.
As you can see, all settings for a separate cinema (in the absence of separate fragments) are transferred to the resource files. The design is also replaced at the level of images and resources.
This allows you to create applications for individual cinemas using a wizard (a regular application) that will take a sample of the project, overwrite the settings of keys, colors, names and other information, put it in the correct directory and add it to the overall project. We will only need to compile the project and put it in Google Play (although even this can be automated).
Testing
For autotests Robolectric is used, for UI - espresso.
Fabric.io (ex Crashlytics) is used for testing. Plugin for android Studio is used for integration into the application. Fill to server using the gradle command. Settings for testing:
ext.betaDistributionReleaseNotes=”Release Notes for this build.” ext.betaDistributionEmails="BetaUser@y.com, BetaUser2@y.com" ext.betaDistributionGroupAliases=”my-best-testers”
Tests for applications are performed automatically on Jenkins; if tests pass successfully, the build automatically goes to the fabric. For more information about the testing tools, you can read here:
habrahabr.ru/company/rambler-co/blog/266837Analysis of code coverage by tests and success
Publish to Google play
With a large number of applications, publication takes quite a long time. Currently, apps are being published manually, but the possibility of using the Publishing API
developers.google.com/android-publisher is being considered . According to the developers, this api allows you to:
- —Download new versions
- Change application status (alpha, beta)
- —Change application information
Conclusion
In this article, we talked about the development of the Rambler box office architecture, from if-s to the full-fledged application creation wizard. Selecting the main code in the library or sdk, using the wizard, making changes using resources and adapting the design simplify the creation of branded applications.
Thanks for attention!