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.

CategoriesAndroidAndroid StudioSoftware Development

Android Studio always modifying codeStyleSettings.xml – Fixed

Yesterday a colleague of mine started experiencing a weird issue with their android studio. Whenever they’d build the code, a change would be written to codeStyleSettings.xml file automatically.

The change at the first glance was clearly unrelated to something they had been working and hence they weren’t able to figure out what could be the cause of this issue.

What did the change look like?

<Objective-C-extensions>
  <option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
  <option name="RELEASE_STYLE" value="IVAR" />
  <option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
  <file>
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
  </file>
  <class>
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
    <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
  </class>
  <extensions>
    <pair source="cpp" header="h" />
    <pair source="c" header="h" />
  </extensions>
</Objective-C-extensions>

Clearly it wasn’t something related to our code since we aren’t doing anything with Objective-C in our android project. This made me want to check the android studio config on that machine, to see if some preferences were changed by mistake, or through an update or any other means.

Quickly searching for objective-c, I found that there were changes being written to the Inspections in the IDE. However, the dev denied making any such changes.

What do you think was the next obvious step for me? Google :]

Turns out, this was an old issue that someone asked about, on StackOverflow 3 years ago, and this, was the top rated and accepted answer that led us to the solution.

The <Objective-C-extensions> Section is added by the Android NDK Support Plugin. Which was added in 1.3 and is activated by default. 
If you have activated this plugin it adds the Section to your codeStyleSettings.xml. Otherwise it will be removed.

Answer by devtribe

Even though the dev had not activated this plugin on their own, maybe an update introduced this change and started causing this weird behaviour.

Here are the steps to fix the problem:

Step 1: Open Preferences in your Android Studio. Shortcut on Mac OS is Command + , (comma) and on Windows is Control + Alt + S
Step 2: Goto Plugins and find the Android NDK Support plugin
Step 3: Uncheck the box to deactivate this plugin, As soon as you do that, a warning dialog will appear asking you to disable Android APK Plugin, Click OK
Step 4: Once deactivated, click on Apply and restart your Android Studio!

Now you can remove the file from your git change log if it appears there, restart Android Studio and the problem would be fixed.

Warning: The solution requires you to disable two plugins which you might want to use at a later stage, so keep that in mind before you do that.

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.

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.