CategoriesAndroid

Working with In-App Updates in Android​

Do you know what’s one of the things that makes me envious of web developers? It is the fact that without relying on in-app updates they can release anything anytime and can be sure that all the users are going to see their new code almost instantly.

For software developers writing code for mobile apps the story is a bit different. The code we write for the mobile apps after every release has to go through the stores’ review process and then slowly gets on to the devices of our wonderful users.

 In-App updates by Google Play Core library are here to change the game.

Default Update Behaviour for Android Apps

In theory, updates on Android apps occur automatically, once the following constraints are met:

  • The device is connected to the Internet over Wi-Fi.
  • It is plugged in and charging.
  • The device is idle and not actively used.
  • The app that has to be updated is not running in the foreground.

As per Google Play’s help page, we know that devices check for updates once a day. This means that it will be at most 24 hours before an app update is added to the update queue.

Default Updates: The Reality

While we expect the users to be on the latest versions of our apps, the reality is a tad different. There may be too many apps in the update queue, automatic updates turned off, or the app may have been in the foreground when it was about to get the update.

Analysis of the app version adoption graphs of your releases will teach you you a number of interesting things:

  • Majority of your app versions are never adopted by 100% of your users. (Newly launched apps will be an anomaly in this case.)
  • Adoption acceleration tends to zero when you hit 75%-80% adoption rate.
  • You have to wait for several months if you want, say, 95% of your users to be off of legacy versions of your app.
App version adoption over last 90 Days
App version adoption

For minor changes, and regular updates it doesn’t really matter a lot and it is completely ok to have a slow and gradual adoption. The real need to get majority users on the latest version arises in case you have a critical fix, or if you want to deprecate a particular version due to any reason. That’s when you want to nudge your users to update the app if they haven’t.

How did we Force In-App Updates till now?

If you have been writing apps for at least a year, you might have found various creative solutions to ask your users to get the latest version of the app. Don’t judge me when I mention some that I have personally used:

  • Sending silent PUSH notifications to tell the clients that an update is available.
  • Using clients to POLL the backend to check if there is an update available?
  • Scraping Google Play web page in the app to see if the update is available.

If you or your company use another approach to solve this problem. I’d love to learn more about it. Hit me up on Twitter 👉 @droidchef

What’s new: The In-App Updates API?

In-App Updates is a feature of the Google Play Core Library which allows you to prompt your users to update the app when a new version is available. Minimum requirements for it to work, include:

  • Devices running Android 5.0 (API level 21) or higher.
  • Google Play Core library version 1.5.0 or higher.

Types of In-App Updates User Experience

Flexible Updates

This UX leads the user to a small dialog offering an update which can happen in the background and the user can continue using the app. Once the update has been downloaded and installed, user will be prompted to restart the app so that the changes can come into effect.

It is useful when you have a new feature that you’d want your users to try.

Immediate Updates

This UX leads the user into a full screen dialog, prompting the user to update the app in the foreground and prevent the user from using the app during this operation. In this flow, Google Play will handle the restart operation of the app as well.

They are useful when you have a critical update that you want your users to install.

Next we will learn how to implement Immediate Updates flow into your android apps.

Getting the dependency

If you don’t already use the Google Play Core library, you can add it to your app’s build.gradle file like this. As of writing this article, the latest version of the library is 1.7.1

implementation "com.google.android.play:core:1.7.1"

This dependency is downloaded from Google’s Maven Repository. So make sure you also include that repository in your project’s build.gradle file.

Checking for Update Availability

Before you can actually launch the update flow, you must check if there is an update available for the user. To do that, we will ask the AppUpdateManager about it.

Let us say we want to trigger the prompt from our MainActivity.

This could be a different entry point for your app so choose it as per your need. I will just refer to the MainActivity as our entry point for the purpose of this article.

Now, open your activity file and create an empty method called checkForAppUpdates()

	private fun checkForAppUpdates() {
		// Logic for checking and triggering updates goes here
	}

Logic for checking and triggering updates will go here, so let’s get started.

First, we need to get hold of the AppUpdateManager

	private fun checkForAppUpdates() {
    	val appUpdateManager = AppUpdateManagerFactory.create(context)
	}
    

Next, we ask the manager about the update information.

	private fun checkForAppUpdates() {
    	val appUpdateManager = AppUpdateManagerFactory.create(context)
		val appUpdateInfoTask = appUpdateManager.appUpdateInfo
    }
    

Now the appUpdateInfoTask that we got is an Intent that you will use to check for the update

	private fun checkForAppUpdates() {
    	val appUpdateManager = AppUpdateManagerFactory.create(context)
		val appUpdateInfoTask = appUpdateManager.appUpdateInfo
        appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
          if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
              // Request the update.
          }
    }
    

Starting an Update

In case there is an update available, we we will request android to start the activity for the update

	private fun checkForAppUpdates() {
    	val appUpdateManager = AppUpdateManagerFactory.create(context)
		val appUpdateInfoTask = appUpdateManager.appUpdateInfo
        appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
          if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
              appUpdateManager.startUpdateFlowForResult(
              	appUpdateInfo,
              	AppUpdateType.IMMEDIATE,
              	this,
              	7500) // REQUEST_CODE_IN_APP_UPDATE = 7500
         }
    }
    

7500 is a request code we are using for the activity we are starting for result. You can choose any number that is unique for your app and can store it in the place you aggregate your Constants.

Getting Ready for the Action

Next, call this method from the onCreate()of your activity.

  	override fun onCreate(savedInstanceState: Bundle?) {
    	super.onCreate(savedInstanceState)
        // ... existing logic
    	checkForAppUpdates()
  	}

With all this code so far, you should be able to get the ball rolling.

Building the APKs or app Bundles

Since we need to emulate a real release process, we need to build an APK or app bundles that we are going to use for testing. Installing the app straight away from Android Studio or command line will not work.

To assemble the release we can simply ask Gradle to do it. Make sure you are in the project directory and then run this command.

First build one version of the app.

Let’s call it 1.0.0 and version code 1.

Then build the second version of the app, which will act as an update for the previous one.

Let’s call it 1.0.1 and version code 2.

If you have a lot of flavours in your project you can also build a specific flavour in case that makes things faster for you to test.

For simplicity, I’ll just run this command.

./gradlew assembleRelease

Finally when this task finishes successfully. You should have your APKs or app bundles ready. I assume you know where to find your release APK or app bundles.

Testing In-App Updates

You need a Google Play Developer account for this step. We are going to leverage internal app sharing feature of the Google Play Console to test our in-app updates. If you’re not familiar with it, I’d highly recommend you to first look at it and then come back to the article. As that link contains some crucial steps related to adding accounts that have access to your test apps, and also how to enable internal app sharing in your Google Play app on your device.

Head over to the internal app sharing upload page. On this page you’ll see an option to upload the release assets.

Internal app sharing page in Google Play console
Internal app sharing page in Google Play console

Uploading APKs and app Bundles

Drag and drop the first version here and once the upload finishes you will have a download link below.

Now do the same thing with your second version and have a link ready before we move on to the next step. The download links on that page should look like this.

Download links section of Internal app sharing page.
Download links section of Internal app sharing page.

Finally make sure you don’t have any other versions of this app on your device.

Installing the First Version

Now carefully copy the link of version 1.0.0 and use it to install this app on your device.

Using this link on the device, will take you to a screen that looks something like this

in-app updates testing step 1

Click on install, to start the installation. Once the installation is finished. Open the app and head over to the activity where you put your in-app update logic.

Nothing should happen at this point. You might be wondering why am I not getting the update? 🤔

This is because Google Play still doesn’t know if an update is available or not.

Notifying Google Play

Next, copy the second version’s link and open that on your device. It should open a screen that looks like this. Please DO NOT click the update button here.

in-app updates testing step 1

This step ensures, Google Play knows that there is an update available.

Now make sure you kill your app from the background.

Updating the app

Start your app and head over to the activity that has your in-app update logic. You will see the in-app update screen soon after the activity appears.

in-app updates testing step 3

Finally you can click on the update button and let Google Play do its magic.

Once the update finishes, your app will restart automatically. You can verify the version name by looking into the app settings as well.

Handling a Stalled Update

As all users are not patient or there might be a scenario where the user closes or terminates your app during the update. Google Play is smart enough to make sure that the update will continue to download and install in the background, however we must ensure that it is applied correctly when the user returns to the foreground.

Fortunately, the library has us covered for that scenario too. Remember how we were able to check if there is an update available using UpdateAvailability.UPDATE_AVAILABLE state.

There is another state that you can compare the availability against which is called UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS.

You can add an OR condition for checking this state too and you should be covered.

	private fun checkForAppUpdates() {
    	val appUpdateManager = AppUpdateManagerFactory.create(context)
		val appUpdateInfoTask = appUpdateManager.appUpdateInfo
        appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
          if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ||
              appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
              appUpdateManager.startUpdateFlowForResult(
              	appUpdateInfo,
              	AppUpdateType.IMMEDIATE,
              	this,
              	REQUEST_CODE_IN_APP_UPDATE)
         }
    }
    

Since the user can just come back from the background without killing the app during the update, you might also want to call the update check from onResume() like this.

  
  	override fun onResume() {
    	super.onResume()
    	checkForAppUpdates()
  	}

Reacting to Update Status Callbacks

If you have a keen eye for the code, 🧐 you might have figured out that we are actually starting an activity for result. This leads us to our final piece of handling failed updates. For simplicity, we just check if the result was not ok, we try to prompt the user for the update again as we are dealing with immediate in-app updates right now.

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
      if (requestCode == REQUEST_CODE_IN_APP_UPDATE) {
          if (resultCode != RESULT_OK) {
              checkForAppUpdates()
          }
      }
  }

Preventing Excessive Update Checks

As a smart developer that you are 😎, I am sure you have a question in your mind. Aren’t we going to check for updates too frequently? The answer is yes, with this approach you will end up checking for updates every time your activity’s onCreate() or onResume() is called.

As we are currently only dealing with Immediate updates, they should only be intended for critical updates. Ideally you should guard the check with some short-circuiting logic.

The way I work around this problem is by using a Feature Flag surrounding the checkForAppUpdates(). The way we intend to use it, is by targeting specific versions of the app. So the flag only returns true for specific version and otherwise false. Using this we can target our users on a specific version to perform an immediate in-app whenever there is a need for one.

  
  	override fun onCreate(savedInstanceState: Bundle?) {
    	super.onCreate(savedInstanceState)
        if (myFeatureFlagManager.isFlagEnabled(FeatureFlag.IN_APP_UPDATE) ) {
    		checkForAppUpdates()
        }
  	}

What did you learn today?

If you’ve followed the post till here, 👏 Congratulations, you’ve equipped yourself with some knowledge about how to use in-app updates from Google Play Core library in Android. If you manage to get this working in your app, I’d love to try it out as a user.

Tell me about your app here 👉 @droidchef

What is Coming Next?

In the next blog, we will learn how to implement and handle flexible in-app updates.

Where to Go From Here?

Want to learn more about the In-App Updates. You can head over to Google’s official documentation here.

If you are facing any problems with testing in-app updates, there is a specific troubleshooting section on the same page.

If you like this post, please show your support by sharing it and if you spot any issues here, don’t hesitate to shoot them my way. Let’s connect on LinkedIn and Twitter.

CategoriesAndroidFlutterTalks

Flutter Add-to-App in Production: A War Story

Are you planning to use Flutter Add-to-App feature in Production? Make sure you carefully listen to my story about using this in production.

Our team at Getaround did a thorough research investigating ways to integrate Flutter into our Native apps using Add-to-App. We successfully managed to ship a feature built completely in Flutter. There were a lot of hurdles we had to cross in our journey. This talk of mine throws light on them. In the end, I also share the future of Flutter at Getaround. What do you think it would be? Did we decide to go big or go home? Answers to all these questions in the video below 👇

Please remember that Flutter is under active development. A lot of things that I mention in the talk may have changed or would no longer be valid. The questions that I pose though, will still be valid for any cross-platform framework you or your team are evaluating.

Also, if you are interested in going over the slides again or referring to them please feel free to do so with proper attribution. ❤️

I have been speaking at conferences worldwide for over 6 years now. The full list of my talks is available here. If you’d like me to present a talk at your Meetup, Conference or at one of your organization’s private events please reach out to me on Twitter.

CategoriesAndroidTalks

Mobile Experimentation Talk at Droidcon

How do I run experiments in my mobile app? What does A/B testing on Android & iOS look like? If similar questions around mobile experimentation cross your mind, you’re in the right place.

Im this post, I present you with my talk from Droidcon Berlin 2019 that will teach you how to efficiently write & setup experiments (A/B tests). Further it will showcase a way to create apps that keep the customers at the centre of everything.

Having run over a 100 experiments alone in the last 2 years at Booking.com. I present some hard to believe facts related to user experience and design that developers often overlook while shipping features.

If you watch this talk you will first learn how to frame the hypothesis of an experiment. Then learn to calculate runtime of an experiment. Followed by tips to set up metrics to measure success. All this so that you can act as a product manager yourself in future!

Think of this talk as a crash course for Mobile Developers to become Product Owners 😄

I am always excited to discuss various aspects of mobile experimentation especially because there is no one way to do it correctly. Different types of mobile experiments pose different challenges. If you or your organisation are have an interesting challenge related to mobile experimentation I would love to hear it out, discuss and learn from.

Also, if you are interested in going over the slides again or referring to them please feel free to do so with proper attribution. ❤️

I have been speaking at conferences worldwide for over 6 years now. The full list of my talks is available here. If you’d like me to present a talk at your Meetup, Conference or at one of your organization’s private events please reach out to me on Twitter.

CategoriesAndroidTalks

Modularization in Android

We have all heard stories about modularization, faster builds, quicker iterations and all the good things we get through it. But when it comes to actually breaking your legacy into modules, your code can give you a serious run for money. In this talk I share how 60+ Android Devs at Booking got together to defeat the legacy and modularized the app to get 10 times faster builds. If you watch this talk you will be able to learn the best practices about modularizing your large code bases and avoid the pitfalls that we discovered in journey.

Also, if you are interested in going over the slides again or referring to them please feel free to do so with proper attribution.❤️

I have been speaking at conferences worldwide for over 6 years now. The full list of my talks is available here. If you’d like me to present a talk at your Meetup, Conference or at one of your organization’s private events please reach out to me on Twitter.

CategoriesUncategorized

Learn how to test your Android app for flaky connections!

Request timed out! But you didn’t expect it, did you? Of course not because while you were writing the app and testing the code you were always on your blazing fast WiFi connection or a 4G LTE network. But in the real world, all your users don’t have access to such a network connection at all times.

Would you want them to suffer?
Would you want your app to behave in a strange manner in that case?
Would you want to create a bad user experience?
Would you want to have an unsatisfied customer?
Do you always test all your features depended on network requests for flaky connections?

If your answer to all the above questions is “No”, then I have a simple solution for you, that’ll make your life a lot easier to test your features for these scenarios and have a failure mechanism in place to be more responsive to the user.


Implementation

The solution is pretty straight forward, create an Interceptor for your network requests and delay or fail them. Let’s look at how to implement this interceptor with OkHttp and provide easy access to it through a UI to all the stakeholders that are responsible or are willing to test your application.

public class NetworkThrottlingInterceptor implements Interceptor {

private static boolean failRequests;

private static final AtomicLong failRequestCount = new AtomicLong(Long.MAX_VALUE);

private static boolean delayAllRequests;

private static long minRequestDelay;

private static long maxRequestDelay;

private final Random random = new Random(4);

@Override
public Response intercept(Chain chain) throws IOException {
if(failRequests) {
long failC = failRequestCount.get();
if (failC > 0) {
failRequestCount.compareAndSet(failC, failC-1);
throw new IOException("FAIL ALL REQUESTS");
}
}
if(delayAllRequests) {
long delay = minRequestDelay;

if(minRequestDelay != maxRequestDelay) {
delay = (long) ((random.nextDouble() * (maxRequestDelay - minRequestDelay)) + minRequestDelay);
}
long end = System.currentTimeMillis() + delay;
long now = System.currentTimeMillis();
while(now < end) {
try {
Thread.sleep(end - now);
} catch (InterruptedException e) {
// Nothing to do here, timing controlled by outer loop.
}
now = System.currentTimeMillis();
}
}
try {
return chain.proceed(chain.request());
} catch (Exception ex) {
if (BuildConfig.DEBUG) {
Request request = chain.request();
Log.e("NETWORK", "Exception during request", ex);
Log.e("NETWORK", "Request was to: " + request.url().toString());
}
throw ex;
}
}

public static void delayAllRequests(long minRequestDelay, long maxRequestDelay) {
if(minRequestDelay == 0 && maxRequestDelay == 0) {
delayAllRequests = false;
} else {
NetworkThrottlingInterceptor.minRequestDelay = minRequestDelay;
NetworkThrottlingInterceptor.maxRequestDelay = maxRequestDelay;
delayAllRequests = true;
}
}

public static void failNextRequests(long failCount) {
if(failCount == 0) {
failRequests = false;
}
else {
failRequestCount.set(failCount);
failRequests = true;
}
}
}

The logic in the interceptor is quite simple, but for verbosity, I’ll still explain it here:

We have two scenarios that we deal with, through this interceptor:

  1. Delay a response for a given network request.
  2. Fail next n network requests.

Scenario 1: Delay a response for a given network request

For this scenario, we set a minimum and maximum value and for a given request we find a random value between this range and ask the thread to sleep for that time.

You can create a different combination of settings that you give access to, through your UI, for example:

  1. Good Network (min = 0, max = 0)
  2. Slow Network (min = 1 second, max =5 seconds)
  3. Very Slow Network (min = 5 seconds, max = 10 seconds)

By default, your app can always be set to work on the Good Network. And then the users can switch to other networks as and when needed.

Scenario 2: Cause next n network requests to fail

For this scenario, we maintain a fail counter that keeps decreasing on each request until it becomes zero. And while this happens we just throw an IO Exception to cause the network request to fail, you can even cause a failure by other means like creating a fake failure response with some status code 5xx.


Interceptor Integration

Injection of this interceptor is quite simple you can add it at the time of configuration of your OkHttp client instance like so.

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new NetworkThrottlingInterceptor())
.build();

Interceptor Exposure

In our app, we do it through a debug screen that contains a simple Spinner widget that holds all these values that we can select and modify the interceptor configuration at runtime, the code for which looks like this

private void setupNetworkThrottler() {
spNetworkInterceptor = findViewById(R.id.sp_network_throttle);
ArrayAdapter<String> networkSpeedTypeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line,
new String[]{"Good network", "Moderate network 1-5s delay", "Poor network 5-10s delay"});
spNetworkInterceptor.setAdapter(networkSpeedTypeAdapter);
spNetworkInterceptor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0: NetworkThrottlingInterceptor.delayAllRequests(0,0);
break;
case 1: NetworkThrottlingInterceptor.delayAllRequests(1000, 5000);
break;
case 2: NetworkThrottlingInterceptor.delayAllRequests(5000, 10000);
break;
}
}

@Override
public void onNothingSelected(AdapterView<?> parent) {
NetworkThrottlingInterceptor.delayAllRequests(0,0);
}
});
}

If you have other interesting solutions that you can leverage this interceptor for, please share them with me on Twitter where you can find me as @droidchef.

I’d like to give credits to Jonathan Farris, who originally came up with this solution.

CategoriesAndroidTalks

How to Love Your Developers Like Your Customers

Abstract:

In this talk I’ll show how you can increase developer productivity of your Android Teams by adding simple debug features to your app, hacking gradle and Android Studio to turn your Devs into Super Heroes who ship much better products faster than ever. Everybody talks about Design, Architectures, Libraries and what not, but, what we often forget is that most of the times quick wins for us as developers can be much more beneficial than tackling large refactors to use the latest technologies available so if we apply the 80-20 rule to our development workflow we can achieve so much more. People with any level of android experience can see this talk.

Also, if you are interested in going over the slides again or referring to them please feel free to do so with proper attribution. ❤️

I have been speaking at conferences worldwide for over 6 years now. The full list of my talks is available here. If you’d like me to present a talk at your Meetup, Conference or at one of your organization’s private events please reach out to me on Twitter.

CategoriesUncategorized

Making the most out of Android Studio Debugger

This is a trick I learnt very recently from a Senior Android Developer at my company and now I feel miserable about all the time I had spent waiting for Gradle builds to test my changes while writing Android Apps.

Here is a typical scenario every android developer would have come across at least once during their development lifetime. You have a List of items that you want to show in a ListView or RecyclerView.

Below is our beloved onBindViewHolder method that binds your model to your views of the RecyclerView.

    @Overridepublic void onBindViewHolder(ViewHolder holder, final int position) {final String name = values.get(position);        holder.txtHeader.setText(name);        holder.txtFooter.setText("Footer: " + name);    }

Now lets say you wanted to change the text color for every 3rd element in the list. So the code would look something like this

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {

final String name = values.get(position);
holder.txtHeader.setText(name);
if (position % 3 == 0) {
holder.txtHeader.
setTextColor(Color.GREEN);
}
holder.txtFooter.setText("Footer: " + name);
}

Then you’d click on Run and wait for build to finish and see your changes, right?

Copied Image from Anand Shekhar Roy’s post on Speeding up your gradle builds.

Now you’d be thinking what other way could we achieve this?

Welcome Android Studio Debugger, yes we need no external plugin or tool to achieve the above task and more over, we won’t even have to build the project, you heard me, we will by pass Gradle 🙂 Here’s how !

Step 1 — We need to define a Run Config

This run config would allow us to launch our app and attach the debugger from android studio to it, alternatively you can also attach it to an already running process by your hand.

Click on Run -> Edit Configurations

On the Top-Left Corner of the Dialog, Click on the + icon and choose Android App

Now give it a name, I like to call it Run-Only, but you can call it anything.

Choose the module which has your app code, in the below screenshot it is called app.

Important step : 
In Installation Options, Choose Nothing for Deploy.
In Launch Options, Choose Default Activity
In Before Launch, Remove the Gradle-aware Make task.

So the config should look like this screenshot below

Now you can apply and save this config. It should be selected automatically by Android Studio now, if not just select it from the list.

Now apply a Breakpoint some close before the line you’d like to test a change, in our case we’ll put it where we set the text.

Right Click on the Break Point and Uncheck Suspend. Please write back to me on Twitter, if you have never seen this before, I’d very happy to know I showed you something new 🙂

As soon as you uncheck this dialog, you would see this dialog expand with a lot of options.

What we are interested right now is in Evaluate and log. We will write a statement there to test a change in our RecyclerView’s Item. Click on the small blue colored icon next to the dropdown arrow of the Evaluate and log text input box to expand it to a bigger editor and add your testing statement, like this and click Ok and then click on Done.

Now Click on Debug icon with Run-Only Config Selected and see the Magic.

The App Should start from your default activity and you should see the changes applied there, also if you pay close attention to the IDE, on the very bottom you’ll just see one task running that says Launching Activity.

Would love to hear your experiences when you try this trick !! My Twitter Handle is @droidchef

CategoriesUncategorized

How to navigate through your java projects on Github like a boss?

If you are an Android or a Java Developer you’d know about the pain of navigating amongst the classes of a project on Github. You either need to look it up in the tree like this


or use the file finder using the keyboard shortcut “ t ” like this


But while you are looking at the code, and want to jump to another class you have to use one of the above methods unlike what you do in the IDE. You just Cmd + Click and reach the other class, like this

In order to bring the similar kind of navigation to Github, I wrote a chrome extension which will automatically hyperlink all the classes of your project so you can just click to open them in a new tab.

Here it is in action

Installation is simple, just click on this link and once installed just open any of your projects on Github and navigate through the classes like a boss 😎

Wait…there’s more to it! It also links all the classes from the Android SDK to the official documentation, which means now you have almost everything you need to understand the code on Github at your disposal with just a click.


I would love to your hear feedback on this effort. If you like it, just hit the ️💚 icon below and let your friends know about this extension.

The extension is open sourced already, feel free to star and/or watch it, report issues, improvements and features. I’d be more than happy to accept pull requests there.

Github Repo : https://github.com/droidchef/hops

Follow me on twitter : https://twitter.com/droidchef

Cheers!