Android applications can grow very quickly. As they are composed of components (Activities, Services and Content Providers), it should be easy to extend an existing application to create new applications by just reusing already developed components. Recently, the akquinet Mobile Team had to develop several versions of the same application with a different set of features. This might sound easy, but Android does not really support such kind of modular development. Indeed, we faced several issues coming from the Android development model.
This blog post explains the encountered issues and how we fixed them by using rindirect. This tool allows, inside an Android application, to integrate components defined in other Android applications. This reuse pattern is close to impossible to achieve without rindirect or implies major limitations. Rindirect is developed by akquinet and is now available as open-source. This tools is well suited for Maven-based development, and so, works pretty well alongside the maven-android plugin.
The problem: reusing code
Let’s take an application App1 containing an activity MyActivity.java. Android applications are identified using a package name (specified in the application’s manifest). During the build process, an R class is created inside the specified package referencing all resources. Let’s say that App1 uses the package foo.app1, and so, the build generates a foo.app1.R.java file.
Our simple activity depends on this R file to access resources:
package foo.app1; import android.app.Activity; import android.os.Bundle; public class MyActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
Now, imagine that we need to create an extended version of App1 called App2. App2 is an application reusing components from App1, and especially MyActivity.
App2 cannot have the same package name, because, as said previously, Android identifies applications from their package name. Having the same package name will forbid users to deploy both versions on the same device. Also, two apps with the same package name cannot coexist in the Android market. Thus, App2’s package name must be different, e.g. foo.app2. So, the build process generates the foo.app2.R.java file. This is perfectly fine for activities developed in the app2 context, because they will use this file, but how to reuse activities from app1?
Let’s try:
- Declare the activity in the Android Manifest
- Create a dependency to get the component code
It does not work. Indeed, those components depend on foo.app1.R.java not foo.app2.R.java. So they won’t compile. Let’s explore the different solutions we have to get this to work:
- Copying the first R file: This doesn’t work as 1) the R file will be deleted by the build process, and 2) the references resource addresses are different.
- Copying all our components source code and editing the import statements: That’s definitely painful. To use the correct R class, too many manual actions are required, we do not profit from app1 improvements in app2 and it paves the road to tricky and painful merges.
- Generating a foo.app1.R.java file delegating on foo.app2.R.java : It’s the rindirect way
The Rindirect approach
The rindirect approach is quite simple; it generates R files to satisfy the compilation dependencies. Those R files are created in the adequate package. The R content (resource references) is delegated to the genuine ‘R’ file of the final application.
So, in the previous example, Rindirect generates the foo.app1.R.java by delegating values on foo.app2.R.java. Indeed, this second R file is generated by the Android build process and reflects the resources of the application app2.
This way has several advantages:
- The code source of the app1 is not modified, just reused, and re-compiled in app2
- If a resource used by a component from app1 is missing in app2, you will immediately see a compilation error
- Modifying / Improving components from app1 will immediately benefit to app2
The only drawback is to call rindirect to generate the missing R files. But this can be completely automated inside your build process.
However, you should avoid resource conflicts: it is highly recommended to prefix your values, ids, and layouts with the application name to avoid conflicts.
Using Rindirect
You can download rindirect from here. Rindirect is an open source project funded by akquinet. You can get the source from the Rindirect GitHub project.
There are two ways to use Rindirect:
- The command line
- A Maven plugin
Both of them are described in the rindirect documentation. Using the maven-rindirect-plugin makes developing modular Android application easy as pie.
Demo
You can find a demo of rindirect in the rindirect project code.
This demo is a simple Maven multi-module project:
- App1: contains MyActivity
- App2: contains HelloAndroidActivity calling MyActivity from the App1
Rindirect is used in the App2 to generate the R files used by the components from App1. It first declares a dependencies on an apksource of App1 (containing the source of the MyActivity).
<dependency> <groupId>foo.app1</groupId> <artifactId>app1</artifactId> <version>1.0-SNAPSHOT</version> <type>apksources</type> </dependency>
Then, it uses the maven-rindirect-plugin to generate the missing R file:
<plugin> <groupId>de.akquinet.android.rindirect</groupId> <artifactId>maven-rindirect-plugin</artifactId> <version>0.4.0</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <phase>generate-sources</phase> </execution> </executions> <configuration> <package>foo.app1</package> <verbose>true</verbose> </configuration> </plugin>
Finally, don’t forget to declare the activity from app1 in the app2’s Android Manifest:
<application android:icon="@drawable/icon" android:label="@string/app2.app_name"> <activity android:name=".HelloAndroidActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- We must declare the activity (fully qualified name), we want to use --> <activity android:name="foo.app1.MyActivity"></activity>
That’s it ! The App2 now embeds the code of the activity from App1 and is able to call it.
Conclusion
This blog post has shown how we can reuse components from Android applications inside others Android applications and so make Android development modular. Modularizing Android development was critical for us in order to improve our efficiency and quality. However, Android tools do not really support such model, making rindirect necessary. The described mechanism and tools are really powerful and are used in production by the akquinet Mobile Team to manage different versions of the same application with slightly different features. All versions are reusing components from others.
Rindirect, the presented tools, is available under the Apache License 2.0. The project is funded by akquinet A.G.
Hi,
This is definitely right. However, using this option is not really convenient. I don’t think ADT is supporting it. The maven-android-plugin doesn’t. So, this would require manual packaging step (or using Ant which should probably support it).
Interesting.
One thing to note is that APK package IDs must always different, but the Java package names don’t have to be.
You can build two separate APKs, both with unique package IDs from the exact same source (including manifest, R file etc.).
The “–rename-manifest-package” flag of aapt lets you do this.