Building an Android Library Tutorial
See how to create an Android library using Android Studio, publish the library to a Maven repository on Bintray, and host the library in the public JCenter repository. By Nishant Srivastava.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Building an Android Library Tutorial
30 mins
Using your Android library
You have already seen three ways of referencing an Android library in your app projects. They are summarized as:
- Adding as module dependency:
// inside app/build.gradle file implementation project(':validatetor')
- Adding as a dependency from a remote Maven repository, i.e. a Bintray hosted Maven repository:
// project's build.gradle file, under allprojects/repositories maven { url 'https://dl.bintray.com/nisrulz/maven' } // inside app/build.gradle file implementation 'com.github.nisrulz:validatetor:1.0'
- Adding as a dependency from a public Maven repository, i.e. JCenter:
// inside app/build.gradle file implementation 'com.github.nisrulz:validatetor:1.0'
// inside app/build.gradle file
implementation project(':validatetor')
// project's build.gradle file, under allprojects/repositories
maven { url 'https://dl.bintray.com/nisrulz/maven' }
// inside app/build.gradle file
implementation 'com.github.nisrulz:validatetor:1.0'
// inside app/build.gradle file
implementation 'com.github.nisrulz:validatetor:1.0'
But what about if you have a local AAR file?
First, you need to drop your AAR file inside the app/libs folder.
Then to add the local AAR file as a dependency you need to add the below to your app/build.gradle file
dependencies { compile(name:'nameOfYourAARFileWithoutExtension', ext:'aar') } repositories { flatDir { dirs 'libs' } }
Then sync your Gradle files. You will have a working dependency. Cheers!
Best practices for building Android libraries
Hopefully, you now have an understanding about building and publishing an Android library. That’s great, but let’s also look at some of the best practices to follow when building Android libraries.
Ease of use
Avoid multiple arguments
Minimize permissions
Minimize requisites
Support different versions
Provide documentation
When designing an Android library, three library properties are important to keep in mind:
Don’t do this
Instead do this
Or use the Builder pattern:
Every permission you add to your Android library’s AndroidManifest.xml file will get merged into the app that adds the Android library as a dependency.
Requiring a feature by declaring it in the AndroidManifest.xml file of your Android library, via
means that this would get merged into the app AndroidManifest.xml file during the manifest-merger phase of the build and thus hide the app in the Play Store for devices that do not have Bluetooth (this is something the Play Store does as filtering).
So an app that was earlier visible to a larger audience would now be visible to a smaller audience, just because the app added your library.
The solution is simple. Simply do not add the
line to the AndroidManifest.xml file for your Android Library. Instead use the following Java code snippet to detect the feature from your library during runtime and enable/disable feature accordingly:
A good rule of thumb: support the full spectrum of Android versions with your library:
Internally detect the version and enable/disable features or use a fallback in the Android library:
-
Ease of use
When designing an Android library, three library properties are important to keep in mind:
- Intuitive: It should do what a user of the library expects it to do without having to look up the documentation.
- Consistent: The code for the Android library should be well thought out and should not change drastically between versions. Follows semantic versioning.
- Easy to use, hard to misuse: It should be easily understandable in terms of implementation and its usage. The exposed public methods should have enough validation checks to make sure people cannot use their functionality other than what they were coded and intended for. Provide sane defaults and handle scenarios when dependencies are not present.
-
Avoid multiple arguments
Don’t do this
// Do not DO this void init(String apikey, int refresh, long interval, String type, String username, String email, String password); // WHY? Consider an example call: void init("0123456789","prod", 1000, 1, "nishant", "1234","nisrulz@gmail.com"); // Passing arguments in the right order is easy to mess up here :(
Instead do this
// Do this void init(ApiSecret apisecret); // where ApiSecret is public class ApiSecret { String apikey; int refresh; long interval; String type; String name; String email; String pass; // constructor // validation checks (such as type safety) // setter and getters }
Or use the Builder pattern:
AwesomeLib awesomelib = new AwesomeLib.AwesomeLibBuilder() .apisecret(mApisecret).refresh(mRefresh) .interval(mInterval).type(mType) .username(mUsername).email(mEmail).password(mPassword) .build();
-
Minimize permissions
Every permission you add to your Android library’s AndroidManifest.xml file will get merged into the app that adds the Android library as a dependency.
- Minimize the number of permissions you require in your Android library.
- Use Intents to let dedicated apps do the work for you and return the processed result.
- Enable and disable features based on whether you have the permission or not. Do not let your code crash just because you do not have a particular permission. If possible, have a fallback functionality when the permission isn’t approved. To check if you have a particular permission granted or not, use the following Kotlin function:
fun hasPermission(context: Context, permission: String): Boolean { val result = context.checkCallingOrSelfPermission(permission) return result == PackageManager.PERMISSION_GRANTED }
-
Minimize requisites
Requiring a feature by declaring it in the AndroidManifest.xml file of your Android library, via
// Do not do this <uses-feature android:name="android.hardware.bluetooth" />
means that this would get merged into the app AndroidManifest.xml file during the manifest-merger phase of the build and thus hide the app in the Play Store for devices that do not have Bluetooth (this is something the Play Store does as filtering).
So an app that was earlier visible to a larger audience would now be visible to a smaller audience, just because the app added your library.
The solution is simple. Simply do not add the
line to the AndroidManifest.xml file for your Android Library. Instead use the following Java code snippet to detect the feature from your library during runtime and enable/disable feature accordingly:// Runtime feature detection String feature = PackageManager.FEATURE_BLUETOOTH; public boolean isFeatureAvailable(Context context, String feature) { return context.getPackageManager().hasSystemFeature(feature); } // Enable/Disable the functionality depending on availability of feature
-
Support different versions
A good rule of thumb: support the full spectrum of Android versions with your library:
android { ... defaultConfig { ... minSdkVersion 9 targetSdkVersion 27 ... } }
Internally detect the version and enable/disable features or use a fallback in the Android library:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Enable feature supported on API for Android Oreo and above } else { // Disable the feature for API below Android Oreo or use a fallback }
-
Provide documentation
- Provide a README file or a Wiki which explains the library API and its correct usage.
- Include javadocs and other comments in the code wherever you see the need. Your code will be read by others so make sure it is understandable.
- Bundle a sample app that is the most simplistic app showcasing how the Android library can be used. The sample project you used in this tutorial could serve as an example project.
- Maintain a changelog
// Do not DO this
void init(String apikey, int refresh, long interval, String type, String username, String email, String password);
// WHY? Consider an example call:
void init("0123456789","prod", 1000, 1, "nishant", "1234","nisrulz@gmail.com");
// Passing arguments in the right order is easy to mess up here :(
// Do this
void init(ApiSecret apisecret);
// where ApiSecret is
public class ApiSecret {
String apikey; int refresh;
long interval; String type;
String name; String email; String pass;
// constructor
// validation checks (such as type safety)
// setter and getters
}
AwesomeLib awesomelib = new AwesomeLib.AwesomeLibBuilder()
.apisecret(mApisecret).refresh(mRefresh)
.interval(mInterval).type(mType)
.username(mUsername).email(mEmail).password(mPassword)
.build();
fun hasPermission(context: Context, permission: String): Boolean {
val result = context.checkCallingOrSelfPermission(permission)
return result == PackageManager.PERMISSION_GRANTED
}
// Do not do this
<uses-feature android:name="android.hardware.bluetooth" />
// Runtime feature detection
String feature = PackageManager.FEATURE_BLUETOOTH;
public boolean isFeatureAvailable(Context context, String feature) {
return context.getPackageManager().hasSystemFeature(feature);
}
// Enable/Disable the functionality depending on availability of feature
android { ...
defaultConfig {
...
minSdkVersion 9
targetSdkVersion 27
...
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Enable feature supported on API for Android Oreo and above
} else {
// Disable the feature for API below Android Oreo or use a fallback
}