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.

2.7 (7) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

  • 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.
  • 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
    }
    
  • 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
  1. // 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();
    
  2. // 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
    
  3. 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
    }
    
// 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
}
Nishant Srivastava

Contributors

Nishant Srivastava

Author

Joe Howard

Final Pass Editor

Over 300 content creators. Join our team.