 Good afternoon, everyone. Thank you all for coming to this talk. I'll be explaining about the best practices and experiences that I've had while I was trying to learn to build Android SDKs or libraries. I'll be covering a lot of these information, like in the upcoming slides. But let me first introduce myself. I'm Nishant Shavasthu. I am a software developer at Omni Labs. Our company is based out of Vancouver and San Francisco. It's a small startup. And what it deals in is it builds analytics and business intelligence tools for marketers. You can obviously find me at my Twitter handle and GitHub handle, that's NISRULZ. While at work and in open source, I've built a lot of libraries. And two of those which I'm really proud of that I've built in open source are basically Sensee and EasyDeviceInfo. So Sensee is a very basic Android library which makes detecting gestures, such as a wrist twist or your shake detection or your tilt or rotation or all that stuff. It makes it super simple. You just have a particular listener and it just tells you that a particular gesture is there. So I made this library that was really something that I wanted to build in the first place. And then EasyDeviceInfo was basically born because I have done a lot of analytics SDK. And that requires me to get a lot of information regarding device information. So that's all good, right? But wait, what am I talking about? What is an Android library? So an Android library, according to me, is basically like it's a reusable piece of logic that is written in code. But the way I like to put it is like an Android library is basically made of three secret ingredients combined when combined together. It forms the Android library. The basics that we all know about is basically the Java code, which if it is being used in Java language itself, you can have the jar that is Java archive. Android library has extra stuff in there that is Android resources and Android manifest stub. Stub is basically a placeholder. When you combine all these, it forms an Android archive. In short, AAR, that's the Android library. But why would you want to create an Android library in the first place? It doesn't make sense. I don't just want to create an Android library, right? Well, the short answer that I would like to add is like, you don't have to, because if there's already a solution existing, why then just use that? It might sound a bit obvious right now, but it is not. Just take for example, you have all these huge number of libraries just built for runtime permissions, right? You don't want to have all those. We should literally be having just one the same way as we have retrofit and OK, should it be existing for networking, right? So if you really want to contribute back, maybe send a pull request to these specific ones, but the one library that you are trying to build around, maybe an already existing solution exists, and you just send a pull request and make it better. With that said, let's just move on to best practices for building Android libraries. So before I start talking technical stuff in this, I would like to mention a few aesthetics about how and why a particular type of Android library should be built. And it starts with the ease of use. So when I say ease of use, I'm literally trying to say that the code that is reusable, it should be very easy to use. And it needs to follow three simple rules. They can't be more, but these are the major ones that I look forward. And that is that it should be intuitive, as in it should do exactly what it is saying that it is going to do. Take, for example, you build an Android library that is supposed to make image cropping easier for Android developers, right? But you added another version to it, and then you added like QR code to it. That does not make sense because initially, the prime focus of the library was to do just image cropping stuff. So it should be intuitive. It should do exactly what it is meant to do. It should not deviate from its path. The second is basically it should be consistent between versions. So in the first version, maybe you had your image cropping functionality. The second version, you added QR code reading, which is obviously conflicting the first rule. But these two versions that change, and then the third version, you replace the image cropping functionality. That's not how it is done. Why? Because the Android developer is still expecting that feature exist, where it doesn't in the version three. So it should be consistent. If you really want to remove something, you need to follow a certain path, which we'll be talking about in the upcoming slides. The last one that I usually look forward is like it should be easy to use and hard to misuse. Now, again, this line is, again, very obvious. But you need to think from the perspective of a library developer versus an Android app developer. A library developer needs to make sure that it is very easy for the app developer to use this particular library code itself. But he also has the responsibility to make sure that even if the app developer tries to misuse it, he can't, because you put in the validation checks for all the different type of scenarios that might exist. OK, so now I'm getting into more technical stuff. And it is more about what are the things that you should not be doing versus what you should be doing. So the first one that I want to start with is avoid multiple arguments. The code line that you're seeing right now, it has multiple arguments. Why is it wrong? Well, because it's easy to mess up the order that you can see. In the example that I'm showing right now, the second argument should be an integer, but it's a string right now. You can check it from the first one. Intrefresh is now prod as a string, which is very easy to mess up. Well, if this is not what you should be doing, then what you should be doing is, maybe you can use an entity class. An entity class is basically something like this, wherein you can put your validation checks. Remember the thing that I was talking about? Hard to misuse. This is how you handle all that stuff. So you have the option of providing all the variables in there, and then you can have the setters and getters. You can have constructors and all this stuff. That's the first way of doing it. Or you can basically use a builder pattern. Builder pattern is something more like a design pattern that you can obviously study, like go online and check out. But it is basically used to initialize your library. If you want to generate a particular instance of a class, you're going to use builder pattern. The good thing about this is everything that we did in an entity class, we can do it in builder class too. Minimize permissions is the second one. Well, most of the time, this is also common to app developers, but whatever I'm actually talking about right now is from the perspectives of a library developer. So you need to keep in mind of those things. It's very common to have all these different permissions that your library might need, but you should not be doing that. I've learned it the hard way. The other solutions that you have to do, you can obviously bypass this by using intents. You don't need permissions to use intents. That's the Android framework is built to have a very good messaging system using intents. So you can use that. You need to reduce the number of required permissions. Now, this line is very specific here is because when you are building a library, you have that Android manifest tab, right? So you can add request, the required permissions in that manifest. When the merging of the manifest happened, of your app and the library, that's when that permission request will basically be embedded in the manifest of your app. So you don't want that to happen. The way to handle that is by checking it dynamically. You can use fallbacks after you have already known it. So instead of adding it in the manifest file, you actually did it like a dynamic check to find out if the permission exists or not. Based on that, you can enable and disable the functionalities. The second one is basically a third one is basically minimize the feature request. So this line that you are seeing, for those of you who don't know, if you include this in your app, your app is filtered out in Play Store from devices that do not have this hardware. So think of this. If as a library developer, I include this in my manifest and during the merging phase, it gets added to your app, you lose the target to other devices that you would usually have. So we don't want this to go in your app's manifest either. So what's the solution? Well, the solution is to do, again, dynamic checking where we can use package manager to check for the system feature. Does it exist or not? If it does, obviously go forward with whatever functionality that you have implemented. If it doesn't, then either use a fallback or maybe disable the functionality altogether. Support different versions. My rule of thumb is basically support the full spectrum of Android versions. Why do I say that? Well, the whole idea is to reach maximum app developers. You want people, those who are still using API version 9 till, say, 14, and then there are people, those who are using from 14 till 21. So you have to support both type of Android developers. So that's why you should be supporting. The min SDK should be 9 and the target SDK should be 26. But what this comes up with is there are other APIs that you cannot access in, say, version 14 or below, or you can access something that is only above version 26. So the way to handle that in your library code is by doing, again, a dynamic check. You can check if a particular version exists. And if that is, then what are the APIs supported by it? If a certain API is not supported by it, you enable and disable functionality based on that. And you add more maybe like a fallback also. You can generate a log in your log cat by showing that, OK, this particular functionality is not available. We cannot do anything with that. But keep in mind, we are not enforcing it on the app developer anymore. We are making sure that everything that we are going to do in the library is happening dynamically. Don't log in production. I've seen this in multiple libraries that are open source. What these guys have literally done is they add logs in the library code. And when you add that library in your Android app, these logs show up. These logs show up even though you haven't enabled any of these logs in your app itself. So you don't want to do that. But you literally want to add logs for exception centers. Why? Because that's important. That needs to be shown to the app developer when he's developing the app. But that's not a good idea to show it all the time, right? You still need to give them some control over who sees these logs. So my way of solving this is by providing a flag logging support. While you're initializing your app, you can provide a buildconfig.debug flag. That is basically that you are in your debug mode, right? And you'll see that the section in my library code, this is my library code right now when I'm initializing it. I flagged it by putting an if condition. And based on that, only my exception would be printed on the log side. So thereby, I'm giving control to the app developer to control when he wants to see this particular log or exception, versus just showing it in the production itself, OK? Degrade gracefully on error. So I'm going to talk about an example first before I talk about what it is here. But this is a problem that I've seen in some of the other Android libraries that I've worked with. One of the other SDK that I worked, it was from Enterprise itself. And what they did was whenever their SDK would fail, it would crash the app along with it. So you do not want to crash the app. If and if your library crashes, you need to cache that error and do something about it, like basically disable the functionality. It doesn't work. You don't want to keep it in an unstable state. Saying that your library is crashed is a problem that the app developer cannot solve. What he can do is come back to you by saying, OK, this is an error, and you need to fix it. And that's a long chain to go through. You don't want that whole process to proceed. So what you want is maybe cache this error, but disable the functionality. When I talk about the error section, this is also important. I see this a lot of places. People have done, like, they cache exception as the superclass. Every single type of exception that comes, just cache that, print it in log, or enable, disable, everything. This doesn't work. Why? Because you don't know what's happening. Unexpected errors are coming, and you are not prepared for that. What you should be doing is doing specific exceptions. You know what is going to happen. Because you know what is going to happen. You know what type of fallback or what type of functionality to disable. If anything is happening outside this, it's a critical problem. Instead of catching it, and just silently not telling anyone about what happened, or maybe just logging it to the app developer side, doesn't help. So you need to be specific as to what kind of exception that you are passing or you are catching, actually. Handle poor network conditions. Now, this is synonymous to people, those who develop apps also. But the perspective is different here. The example that I will take is the first one that I took, the image cropping library. So say, for example, you have an image cropping library, and it downloads a configuration over network. It does that as soon as your app initializes. Now, what is going to happen is, when you start developing it, you will see that the configuration files download instantly because you're in a place where the network condition is good. And it works. And you ship the library. A lot of developers are using it. But what happens is that you need to acknowledge that unreliable network also exists. There are countries that do not have enough bandwidth or good internet conditions. So in those cases, what is going to usually happen is that the configuration file doesn't download. And your library never initializes. It goes into unstable state. It crashes, probably takes the app down too. So the first foremost thing to understand is that you need to acknowledge that the unreliable network conditions exist. Based on that, you need to make some different changes in your code so that you understand that sometimes the configuration is not going to be available. A default solution, you can provide standard default configurations to make use of to initialize your library. That could be a good solution. The other solutions basically include by batching network calls or prefetching data ahead of time and using better data structures such as flat woofers. So all these three things that I'm talking about, batching network calls, prefetch data ahead of time or use better data structures such as flat woofers, they all pertain to one specific thing is that your library should be doing least impact to the app itself. When a library is included in the app, then if you are doing batch network calls, you are using less radio, so less battery. If you are prefetching data ahead of time, you already have the information so you don't have to make multiple calls. If you are basically using better data structure, then the data that is passing over the network as well as what's being used on your device in memory, like it is an efficient way of accessing the memory itself. So all these things are important as a library developer because you are trying to push forward the least impact to the app. If someone is including my library and I start making a lot of network calls, the app developer never coded this functionality in place. And the battery that is being consumed shows up in his app, not in my library. So it's really important as to the things that we are trying to do versus what comes up in the app itself. This is fairly new. So implementation API versus compile. Most of the stuff that you have been doing up till Gradle 4.0 was compiled, and it didn't allow you to do a lot of things. It's like standard compile and the dependency name. What Gradle 4.0 onwards they introduced was the deprecated compile, and they said there are two keywords now for build.gradle file to include dependency. It's implementation and API. Implementation is, think of it, if a library module uses implementation to include another dependency, then it is only available to the library module. It is not going to be available to the app module. But if I was to use an API version of this keyword to include a dependency, this particular dependency would be available to the app module too. So it's just about the scope of where the classes are available, require minimum dependencies. So I say this, and you can use this particular plugin. I use this a lot to keep track of what is the method count, because method counts. So basically every single time that you add a dependency, you are adding extra methods to your own library too. And in total, when your whole library is being used in an app, the sum total of all the dependencies plus whatever methods you have, that gets added to the app itself. So the app developer is most susceptible to be reaching the 64K limit. We want to reduce that. So what we want to do is we want to have less number of these. For example, people usually include the complete Google Play Services library. You don't need that. You need maybe a sub-library of that that is available now. You can see that type of functionality also in EasyDeviceInfo, where we are slated into different modules. And you can include only part of the library itself. Another solution is basically letting the developer make the decision about inclusion. So I think I got this particular information, I guess, like two or three months back. I didn't figure out before this. But the whole point is every single time you add an implementation API or compile statement to your library by adding a dependency, what you are actually doing is you are asking the compiler to compile the library and the dependencies are along with it. What you would want instead is maybe just don't include the library at all. And let the app developer decide if he wants that type of functionality or not. So the way to do that is basically by using provided or compile only. Provided is now deprecated with the new Gradle 2.12 onwards. You can use compile only. What this basically does is that the dependency is not included in your library anymore. And compile only is saying that when the compilation will happen, it will look for the classes at the time of compilation. It's not going to pre-compile these libraries along with the original library itself. So you do this on the library side. But on the app side, you actually include an implementation. Now, if I was not to include this in my app, this dependency is not going to be available to the library itself. So how do I handle that? I handle that by using, again, dynamic checking. Checking for the class, if it exists in the class path. If it does, good enough, we can enable the feature. If it doesn't, the developer didn't include the dependency in the first place, then we just do not do anything. And we can disable it or use a fallback. So it's a clear case of deciding where you want your method counts to go. In this version of doing things, you shaved off the method counts specific to that dependency because you didn't include it in your library. But you asked the app developer to include it. So they made the decision of including it on their site. And you didn't do that. Lifecycle architecture components. So this is fairly new. Everyone talks about this lifecycle architecture component regards to app development. They talk about how to architect your app and everything. But no one talks about the lifecycle architecture comprehensive with respect to libraries. A good thing that it brought with it is now library developers can look for what a particular lifecycle event is going on in the activity itself. And it allows us to trigger a lot of functions based on that. Earlier, we couldn't do that. What we had to do was put it in our readme as to ask the app developer to include a function in onDestroy, onPause, onResume. And they'll have to go and write these functions. But what lifecycle architecture component allows us to do now is we can ask a specific function to get triggered at a specific point in lifecycle itself. So you can see right now we have execute function onCreate, where I have added an annotation on top. And then there's a cleanup function because I don't want to leak memory here. So I have one that is going to run onDestroy. The way I implemented this is by implementing the lifecycle observer class. It's available in the architecture component dependencies itself. And the way that you do it on the activity side is by extending it from lifecycle activities and then adding it as an observer and removing it as an observer. That's it. Any other lifecycle events when they occur, what function needs to get called is decided on your side, and it is not off-roaded to the app developer anymore. It's all being handled by the library developer, and they make the best choices as to where this should be called. This is a good one. So do not hog the startup. So it's all cool. It's nice that you build an awesome library. Everything works. But it's not good if you are going to eat up all the startup time trying to initialize your library. The example that I gave that where you make a network call to get the configuration file, as soon as the app is starting up, if you try to do that instantly, that's not a good thing to do because maybe the UI is not going to be good enough to see that. It's going to be jittery plus maybe the app just stops responding. You don't want that to happen. And app developer, whenever they see that after adding a library, this happens, they'll just remove it. So even though your library is awesome, it does a lot of things. It doesn't have any bugs. You wrote a lot of tests and everything. The first impression it gave was so bad that the app developer just switched it over with a different library. So you don't want to do that. What you want actually to do is do a lazy initialization, but still initialize it at the time when a particular feature is demanded. On-demand initialization is something that you can try doing. Remove functionality gracefully. This is important because no one does this. Every single person that I've seen for not talking about the star Android developers who build libraries, but the general ones who are building libraries nowadays, like the beginners who are doing that, whenever they are trying to remove a public API from their library itself, they just delete it. So it exists in version 1.0. It doesn't exist in version 1.2 anymore. And no one, the app developer doesn't have an idea of how did that happen. So the way to serve can navigate that is by adding at the rate deprecated annotation. That's by marking it that it is going to be removed in future. You do it in one of the versions. And then you remove it after one or two, three versions. You don't remove it instantly. What would be good is if you added a Java doc that explains what to use because this is deprecated. So I'm basically talking about as of release 2.0 replaced by this particular function, so use that. Document everything. So this is important maybe for everyone, like if you are doing it open source. But in general, if you are doing it for your own enterprises also, your code is what is being read all the time. It's not an app which needs to be used by a user. Your code is what is being used by the app developer. So it is read all the time. So if you provide a simple example, if you write enough information for the app developer to understand and think about if do I want to use this or I don't want to use this, providing pros and cons, put it in the read me because that is the first thing that they are going to read. Include Java docs in your code. I think this is very obvious. Bundler sample app. Now, this is something that I need to mention. A sample app needs to be as minimalistic as it can be. Adding a sample app, people think that they can add something that is fancy looking. It has a lot of UI animations and a lot of stuff because that's what people are going to see. But what is important to understand here is that people are not here to see your app anymore. They are here to see the implementation of the API that you have built. So build the simplest possible example app that does not have a different UI or a lot of different things, pomp and show, going on. What you want is it should only show how to implement your Android library, how to use it, and harness all the functionalities that you have invented to the library itself. Maintain a change lock. You can read on this link how to write a change lock. The way I do it on my Sensei library is by including in your project statement how to include this library. And then I add what are the things I added into the library when a particular version was launched. So there can be version 6.5 that had some different type of functionality added, or maybe there were some bugs that I fixed. So I will add all this functionality on GitHub. This is how it is done. You can do it on GitLab and all the other Git repository hosting services too. License is really important. Most of people skip this. But understand this, if you do not have a license, what it literally means nowadays, it's not even like in legal terms, it means that you own the code. And no one is going to come back and contribute back to it. So if you do not mention how you want others to contribute back into it, people are not using your library. They just think it's propriety. So add at least any specific license that you think matches whatever you want to do, and maybe then upgrade it to something else later on. The best one that you can do is basically do MIT or Apache 2.0. That is really good. What I like to do is I like to do a header that basically includes all the stuff regarding my basic summary of the license itself. Because who reads the license anyways? The app developer is basically going to open up the code. And as soon as they open up the code, they see this header. And they understand, OK, this is what the license it is pertaining to, and this is how I can actually contribute back to this library itself. Versioning strategy. Now think of this. What if retrofit had a version that said 1.2 ABC123? You don't want that, right? No one remembers that type of version. So doing these type of different type of versions, you can see a lot of these libraries that go on Jetpack. Some people also put their name. They would put James123 as their version, because Jetpack allows you to do that. But most of the time, you should not be doing that. There's a site, semversion.org, that you can go to check how to version your libraries. The gist is basically that you version your library based on major.minor.patch. If it's a change that breaks something, you bump up the major version. If it is a change that adds a new functionality, you bump up the minor version. If you did something very like a small bug fix, but did not change the public API at all, just bump up the patch version. It's a simple rule. It's not something that is hard to understand. So most people should be following this. They don't. But I hope most people would do that after my talk. Versioning strategy, again. So the versioning that I explained is most about how you should version in the first place, like what should be the version of the name. But then what if you wanted to support two different versions of your same library? Say, since the library that I built, it existed at version 1.0, it did something. And then since 2.0 came out, and it did something else. Now, there's a huge chunk of Android developers who are already using the since version 1.0. And then I come up to since version 2.0. I have two options, too. The first one is basically ask everyone to upgrade to 2.0, which is not easy to do, because most people would not go back and write more code. So that's not how it works. The second version is basically, by changing the group ID, I can release both these versions the same way as RxJava does it. So you can see io.reactivex is the same. But the next group ID is changed. RxJava is now RxJava 2. At this particular point in time, they have released both these versions. And they can push more updates to the version 1 itself and to 2 at the same time. They definitely have given a timeline to kill version 1. So that's how you should be killing out your version. But it says that at the same time, you can use whichever version you want. And they both get their own box fixes in time. ProGuard. So most people actually don't know about this. I've talked to other Android developers, but you can add those particular ProGuard rules in the library itself. Most people just don't do that. And the way to do that is by adding consumer ProGuard files line in your library build.gradle. You just create another file, consumer.proguard.rules.pro, add your configurations in there. And you just add this line. And when you compile and when you try to have your AR generated, that's when this ProGuard file will already be in there. When an app developer uses it and they enable the ProGuard, this configuration is used for your library. But then I read this. And this is by the great Jake Wharton. So basically, he says library providing ProGuard rules is almost always a bad idea. They wind up being overly keeping and defensive so that things just work. And it made me think that he's actually right. These type of things are something that we just can't avoid. We want to keep our library intact. We don't want it to be stripped off by some app developer. But you also need to think that if I didn't write that ProGuard file, like myself, I didn't provide the configuration, then the app developer is basically banging his head to get that right. So it's more like a balance. If you know how to write ProGuard rules, definitely put it in your library. If you don't know how to do it, just too many if I enable and that's it. You just, you need to learn ProGuard rules in the first place to start putting in things. With that, I think that's all. I'm open for Q&A if there is any. Thank you. Thank you, thank you. Do we have any questions? Yes, please come to the microphone. That's a little hard. It's okay, it's okay, I turned it. I have a question about dependency. So say my library needs retrofit to talk to the network. And I'm worried that if someone is using my library and they want to use a different version of retrofit, like how do I make sure that my library will still work? Do I include retrofit? Do I use the... So this is related to the slide that I was talking about. You can actually add a compile-only version where it will be added during the compile time. So it's not included in your library anymore, but it definitely says that there's a reference existing for retrofit and you are looking for that in the application side that someone needs to include that line. So if that particular line is not included, the check that you're doing on your library side is basically checking on the class part that a class exists or not. If retrofit doesn't exist there, then that line was not included in the app in the first place. And you can definitely fall back at that point. So basically I will have to have code that uses retrofit and fall back to use just, like, Android framework. So that's pretty painful. That is, but you are providing a solution here versus... So this is more like a balance that you are trying to have. If you want to include the method counts that retrofit brings in, then you just include it as it is. But if you are trying to avoid that, then the functionality, if it is not retrofit, maybe you are using HTTP UR Connect. And you are not doing a lot of things that retrofit literally does. You are doing a very basic general call, like a get call. At that point, you just don't need that, right? You can just have it as a compile only if that functionality isn't provided by the app developer in the first place, then you are using something else. Okay, well, I'll ask you more later. Okay. Yeah, I have more questions, but other people ask first. Yeah, thank you. Hello, thanks for the talk. Yep. I had a question regarding the sample apps. If it's a library that it's not quite simple to use, that it has lots of functionality, would you, in the sample app, would you ship the sample app with something like MVP and BBM, or would you still keep everything in activity and make like several screens? So since it's a sample app, most developers are going to come and download this sample app first fall and then compile it and then look at the code. They'll not straight away look at the code instantly. They compile it, get it to run first, and that's when it's working. That's when they'll go back and check the code. So you want to make it as simple for them to consume. Think of it. It's like you reading your own code. So if you can split it into different activities, good. Not even activities. If you can split it into different classes, good. If you do not want to do that, then you'll probably put it in the activity. It's an equivalent version of how you want your library to be used. So if it is hard right now, maybe you need to simplify your library code in the first place so that it becomes easy on the app developer side. So the changes that you are expecting it to become easier on the app side, you need to implement that functionality in your library in the first place because then you are changing your perspective, right? Now you want to be in a state where the app developer finds it easy to implement it. So make those changes on the API side so that it is easy for him to see that code itself. My question was more related. If it's a library that requires UI to show it in the sample app, would you use, like, MVP, for example, to divide the logic from the view? Or would that be a bad thing because maybe the user that is using the library won't know about MVP. I would not use MVP on other architecture because now your library is basically an API for simplifying the user interface, right? It's probably providing some sort of, like, a resources file, an XML file, and these things. When you include that functionality and you are trying to show it in an activity, I'll just use it as it is in the activity. As soon as you add MVP and other architecture components and other stuff around it, you're adding more, like, complexity to that sample app. You are obviously making the sample app much better in performance, but that's not the whole idea of the sample app. The idea is to show the implementation of the library. Okay. Thank you. Thank you. Okay, Nishen. Thank you for sharing your insights and your experience with us.