How and why did we embed one Android application into another


A bit of terminology

  • MP – mobile application

  • SDK – Software Development Kit as a term, and also, within the framework of this article, we will call our embedded mobile application so

  • Target MT – MT, into which our SDK was integrated

What for?

So, let’s imagine the situation:

The customer has his own ecosystem and several applications. A new application is being developed as an MVP within this ecosystem. After the launch, the application shows a good result, and they decide to develop it. After some time, the customer decides to expand the functionality of his existing applications at the expense of the recently launched one. In other words, package this application in the SDK and embed it in others.

As you understand, in this case, the SDK is not a single Fragment / Activity and not a set of utilities – it is several dozen screens with a bunch of business logic, a network layer, a database, and a couple of specific features tied to a smartphone camera.

So, let’s imagine a situation: Application N was launched as a separate business case, written from scratch and uploaded. Then, the customer, who has its own pool of mobile applications with its own development teams, decided to integrate the N application inside other applications, thus expanding the functionality. All of these applications exist in the same ecosystem.

Differences from the regular SDK

To begin with, usually any SDK is immediately planned as a separate plug-in / library. Naturally, this affects the architecture of the project and its third-party dependencies (the fewer “left-hand” libraries, the better).

In our case, no one imagined that they would have to integrate into other applications, and the project was developed according to common standards.

In many ways, we were lucky with the technology stack and third-party dependencies – almost all of the libraries used moved the integration without much difficulty. For example, Dagger 2 practically did not create any problems for us (although before that we had to redo all the initialization of the graph). Yandex.Maps and Yandex.Metrica were both in our application and in the target MT, while their instances worked independently and without problems. But Firebase in the SDK had to be abandoned – these Google libraries are not designed to launch two instances at once.

How?

During the integration process, we had to solve many problems, some were simple, some had a non-obvious solution, and some we came across for the first time. Now let’s tell you more.

SDK launch and life cycle

First of all, we had to get rid of the Activity on which our application was originally built. We integrated as an embedded fragment and limited external influences as much as possible. Although, in some cases, calls to Activity have been preserved. Naturally, all the features from Activity had to be transferred inside the fragment (for example, vibration triggering), fortunately, everything was successful without problems.

The SDK itself is easy to launch – just create a configurator object and pass it to a method, getting the result in the form of a fragment object. At the moment of start, the dependency graph is initialized (separately from the target MP graph) and all the necessary callbacks are connected. Then the SDK lives its own life, practically without contacting the target MT.

There is one important point in the implementation of the SDK connection – most requests to the network require authorization, while the SDK itself does not store or receive tokens – this is done by the application into which the SDK is integrated. This method of delivery of tokens was chosen because the SDK and target MTs have a common token format, but the authorization method is different, since each application has its own back and database with users.

Versioning problems

The development process was noticeably complicated by the fact that during the development of the SDK we had to simultaneously support two versions of our old application at once: one version – the current release in the market, where you need to fix bugs and add small features, the second version – a separate assembly with support for translation into English. And later the multilanguage feature was supposed to migrate to the release assembly. Wherein in parallel, the processing of the application is added to the SDK… Agree, a non-trivial situation?

In the process of turning a regular application into a compiled library file, we certainly did not update the codebase. And after a while, the following situation developed:

Naturally, this spread led to problems with merging, keeping up to date and testing. But, in the end, we merged everything safely into one branch.

In an ideal world, we would freeze the development of new features, finalize multilingualism and bring the SDK to release, returning to the release of features later, but this was not possible.

Probably, if the project was multimodular with separation by features, then the situation would be much better – the transfer of new functionality would take less time.

Working with GooglePay

The SDK allows you to pay using GPay (anticipating the question – Huawei Pay is not yet supported), however, to launch the payment screen, you need to execute the method of the AutoResolveHelper.resolveTask class to then get the result inside the Activity.onActivityResult method, and, as you remember, in the SDK, we do not have a single activity!

So this task fell on the shoulders of the MT developers, into which we integrated. In the SDK, we added a method to which you need to pass Intent – the result of a request to GPay, and everything worked without problems and with minimal changes. Thanks to Google for the easy integration.

Dependency Delivery and Problems of Different Architectures

As mentioned above, we are in many ways very lucky with dependencies in applications. The most important plus is that Yandex.Maps were used in all MT, into which we integrated, as well as in our SDK. I didn’t have to rework the screens with the map, and I didn’t have to add unnecessary dependencies. However, besides this there were the following problems:

  1. SDK on coroutines, target MT on RxJava – due to the difference in approaches and architectures, we tried to make sure that the SDK does not interact with external code at all, if possible. But I still had to write callbacks. In some cases, it would be very cool to use the full power of coroutines, but applications had different library stacks, so we used good old listeners.

  2. Breaking changes in different versions of the libraries – Room 2.3.0 in the SDK and 2.2.5 in the target MP. Had to downgrade to SDK. Why everything was breaking, we did not understand, since there were no major changes within the framework of these updates.

  3. Lack of a repository for the SDK – at the development stage it was not possible to use the maven repository to deliver dependencies, so you had to supply an .aar file, and at the same time a list of all libraries used, since third-party dependencies do not store .aar assemblies.

Resources and Manifest Merging

The biggest problem was with the resources. If the SDK and the target MT had resource files with the same naming, then when building, Android left only the file that was in the target MT. Because of this, at least twice, we encountered tricky bugs that took a long time to calculate. And a few more icons were replaced in a similar way, which we did not notice immediately.

This also includes the problem of styles. The target MP had its own theme for the Activity, we have our own. As a result, I had to move the SDK launch into a separate activity so that there were no inheritance conflicts in the themes – some parameters were different for us.

Testing

The main difficulty in testing was that there were several teams, like applications. Each breakdown was different from the previous one, and it was impossible just to get into someone else’s code and debug it – it is not always possible to look into another project.

Therefore, if a bug was found, it was necessary to reproduce it first. Sometimes it worked out, and debugging began with further edits. After that, a new version of the SDK was assembled, transferred to the developers of the target MP, they tested it on their own, and closed the task. Of course, sometimes their bug was repeated even after the edits, and we started all over again.

For example, one of the first problems was the disappearance of icons on the map zoom buttons. It was not possible to understand right away, because no one touched this part of the code during the integration process. It turned out that the application where the SDK was embedded had resources with the same name, but a different color, due to which the resources were overwritten when the code was merged and merged with the background of the button. In the future, we also encountered similar problems with overwriting files.

And when we could not reproduce the bug on our side, the game “Guess the error from the logs” began. In such a situation, the speed of the solution very much depended on how experienced the developer who took up the fix was. Fortunately, we have dealt with all such cases.

So, once, we had to fix a bug that was reproduced only on a group of devices (hello, Huawei smartphones), from the data only logs, in which the chromium engine error without a specific trigger point. After brainstorming, we managed to find out that we used the wrong Context object when initializing the language switch module which caused the application to crash. Thanks to an unknown developer for the saved time and nerves.

Due to the peculiarities of testing, the speed of fixes was low – communication between teams was not instantaneous, hours could pass from the moment the SDK version was built with the fix to the moment of the test in the target MP. Therefore, we tried to test as much as possible on our side in order to reduce the chances of a task being returned.

Advice

If suddenly you have to deal with a similar case of SDK development, then maybe you will save time and nerves by reading this article. We will try to briefly describe what you should pay attention to when starting development.

If you are writing an SDK from scratch and there are a lot of UI features planned:

  1. Name your resource files so that the chance of name matches is minimal. For example, use the prefix in the file name (cool_sdk_fragment_main instead of fragment_main).

  2. Minimize the number of third-party libraries to reduce the chance of version conflicts.

  3. Check in advance if a specific library will work within the SDK. For example, Yandex.Metrica can have multiple reporters to send analytics, but Firebase does not. At the same time, if the MT you are integrating into will use Firebase Performance Monitoring, then Yandex.Metrica will lead to a crash in runtime

  4. Minimize contact with the target MT’s code – the fewer touchpoints, the less integration work for both you and the target MT’s developers.

  5. Log the SDK as much as possible – especially in touchpoints with the target MT – this will be practically the only debugging tool after adding the SDK to a third-party project.

If you are converting an existing application into the SDK, then everything is the same as above, plus:

  1. Try not to release new features since the beginning of the rework in the SDK, so that you do not have to spend a lot of time later on merging and testing. If you have no choice, and new features will have to be done in parallel with the rework, then at least try to rework the SDK in stages so that you can periodically merge new features into the SDK development branch. Easier said than done, but try it anyway.

  2. You will most likely need to redo the entire DI. Even if it seems to you that this is not so, it is better to set aside time, taking into account that you still have to.

Further development of the SDK

We did the main work on turning the application into an SDK and integrating it into other applications. But we still have a lot of work left – refactoring weaknesses, reducing the volume (the size of the target MPs on Android has grown by one and a half times, and on IOS in general by two), detailed logging of the SDK, many minor edits. And there, the release of new features is not far off.

Have you read to the end? Congratulations! I hope our experience in developing and integrating one mobile application into another will help someone else and simplify such a difficult task.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *