Documentation

In-App Updates

Google Play in-app updates for Unity: a drop-in component with a Flexible / Immediate update policy, a simple C# API and an Editor simulator that plays every flow without a store upload.

  • Version 2.0.0
  • Unity 2022.3+
  • Platforms Android
  • Updated Nov 9, 2026
In-App Updates

Overview

In-App Updates asks Android players to update your game without leaving it, using Google's official Play In-App Updates plugin. Drop the InAppUpdater prefab into your first scene: on launch it asks Google Play for an update, picks Flexible (a background download while the player keeps playing) or Immediate (a full-screen, blocking update) with an update policy, and raises UnityEvents for your UI.

Prefer code? The static InAppUpdate API gives you the same flows with C# events, all on Unity's main thread. In the Editor a simulator plays Google Play's side, so every flow, including declines, failures and resuming after a restart, can be tested in Play Mode without uploading a build.

Requirements

ItemVersionNotes
Unity2022.3+With Android Build Support for device builds
External Dependency Managercom.unity.external-dependency-manager 2.0+Resolves Google's com.google.android.play:app-update 2.1.0 for Android builds (see Android dependencies). Google's EDM4U works too. The simulator works without it.
Android deviceAndroid 5.0+ with the Play StoreGoogle Play only offers in-app updates to an app installed from Google Play.
com.unity.uguiAnyThe demo's UI only

Quick start

  1. Import the package Everything lands in Assets/HardArtcore/In-App Updates.
  2. Add the External Dependency Manager Select Prefabs/InAppUpdater.prefab and click Install External Dependency Manager in the Inspector, if it shows (details in Android dependencies).
  3. Add the prefab Drag InAppUpdater.prefab into your first scene. On launch it checks for an update and starts the flow the policy picks.
  4. Ask to restart Hook On Ready To Install to a "Restart to update?" panel whose button calls InAppUpdater.InstallUpdate(). Or tick Auto Install to restart straight away.
  5. Press Play The simulator plays the whole flow in the Editor. Change its answers under Simulator on the component.
Why ask before restarting?

A Flexible update is downloaded in the background, but installing it restarts the app. Google recommends asking the player first, so Auto Install is off by default.

The demo

Open Demo/DemoScene and press Play. The status panel shows the check result, the download progress and a restart banner; the event log lists every event as it arrives. The Update tiles call Check(), StartFlexibleUpdate() and StartImmediateUpdate(); the Simulator tiles change how the simulated player answers, the release priority, and whether the next download fails. The demo works in portrait and landscape.

Demo/DemoScene/Scripts/DemoUI.cs shows both APIs: the restart banner listens to the component's On Ready To Install, and the panel and log listen to the static InAppUpdate events.

Android dependencies

Device builds call Google's official Play In-App Updates library (com.google.android.play:app-update) directly. The package declares it in Editor/InAppUpdatesDependencies.xml, and the External Dependency Manager (EDM) adds it to your Android build, together with the libraries it needs.

Editor/InAppUpdatesDependencies.xml
<dependencies>
  <androidPackages>
    <androidPackage spec="com.google.android.play:app-update:2.1.0">
      <repositories>
        <repository>https://maven.google.com/</repository>
      </repositories>
    </androidPackage>
  </androidPackages>
</dependencies>
  1. Add the External Dependency Manager Select any InAppUpdater and click Install External Dependency Manager in the Inspector, or add com.unity.external-dependency-manager from Window > Package Manager > Unity Registry. The button only shows when no dependency manager is in the project.
  2. Switch to Android The resolver runs on its own when dependencies are missing. To run it by hand: Assets > External Dependency Manager > Android Resolver > Resolve (or Force Resolve).
  3. Accept its prompts If EDM offers to patch mainTemplate.gradle or enable custom Gradle templates, accept.
  • Already using Google's External Dependency Manager for Unity (EDM4U), as a package or a .unitypackage? It reads the same file; keep it, and keep only one dependency manager in the project.
  • With Minify on, the package adds keep rules for the Play classes to the generated Gradle project, so R8 doesn't strip them.
  • Other plugins that use Google's Play libraries (Play Review, Play Asset Delivery, ...) resolve together with this one, so versions don't clash.

The InAppUpdater component

The drop-in way: it checks, decides and starts the flow, and turns everything into UnityEvents.

Check On Start
Ask Google Play when the component starts. On by default.
Check On Resume
Ask again when the app returns to the foreground. Finds Flexible downloads that finished while the game was in the background. On by default.
Auto Start
Start the Play flow automatically when an update is found, once per session (reloading the scene doesn't show the dialog again). On by default.
Default Mode
The mode used unless the update is urgent: Flexible (default) or Immediate.
Immediate From Priority
Treat the update as urgent when its priority is at least this (0-5). 6 = never. Default 4.
Immediate After Days
Treat the update as urgent when Google Play has known about it for at least this many days. 0 = never (default).
Flexible Reprompt Days
After the player declines a Flexible update, don't offer it automatically for this many days. Default 3.
Auto Install
Install a downloaded Flexible update right away, which restarts the app. Off = raise On Ready To Install so your UI can ask first.
Simulator
The Editor's scenario, see Editor simulator.

Events and methods

On Update Available (int)
An update is available. Argument: its version code.
On No Update
Google Play has no newer version.
On Download Progress (float)
Download progress from 0 to 1, for a progress bar.
On Ready To Install
A Flexible update is downloaded. Show a prompt and call InstallUpdate().
On Declined
The player dismissed the Play update dialog.
On Failed (string)
Something went wrong. Argument: the UpdateError name.
Check(), StartFlexibleUpdate(), StartImmediateUpdate(), InstallUpdate()
For UI buttons.
StartUpdateByPolicy()
Starts the update in the mode the policy picks for the last check. Returns false if nothing was started.

The update policy

When an update is found, InAppUpdater picks the mode like this:

  1. Urgent? The update is urgent when its priority is at least Immediate From Priority, or when it is at least Immediate After Days old.
  2. Immediate If the update is urgent, or Default Mode is Immediate, and Google Play allows Immediate for it.
  3. Flexible Otherwise, if Google Play allows Flexible and the player didn't decline it in the last Flexible Reprompt Days.
  4. Nothing If neither applies. The policy never turns a non-urgent update into a blocking one.
Setting a release's priority

Priority (0-5) is set per release through the Google Play Developer API (inAppUpdatePriority on an edit's track release). Play Console has no field for it.

Scripting API

Skip the component entirely if you prefer code. Every event is raised on Unity's main thread.

UpdateFlow.cs
using Hardartcore.InAppUpdates;
using UnityEngine;
using UnityEngine.UI;

public class UpdateFlow : MonoBehaviour
{
    public Slider progressBar;
    public GameObject restartPanel; // its button calls InAppUpdate.CompleteUpdate()

    void OnEnable()
    {
        InAppUpdate.CheckCompleted += OnCheckCompleted;
        InAppUpdate.StateChanged += OnStateChanged;
        InAppUpdate.Declined += mode => Debug.Log("The player said no to " + mode);
        InAppUpdate.Failed += error => Debug.LogWarning(error);
    }

    void OnDisable()
    {
        InAppUpdate.CheckCompleted -= OnCheckCompleted;
        InAppUpdate.StateChanged -= OnStateChanged;
    }

    void Start() => InAppUpdate.Check();

    void OnCheckCompleted(UpdateInfo info)
    {
        if (info.IsUpdateAvailable && info.FlexibleAllowed)
            InAppUpdate.StartUpdate(UpdateMode.Flexible);
    }

    void OnStateChanged(InstallState state)
    {
        progressBar.value = state.Progress;
        if (state.Status == InstallStatus.Downloaded) restartPanel.SetActive(true);
    }
}

InAppUpdate

Check()
Asks Google Play whether an update is available. The result arrives through CheckCompleted; a check already running is not repeated.
StartUpdate(mode)
Starts the Play update flow in UpdateMode.Flexible or UpdateMode.Immediate. Returns false if a flow is already running or the platform isn't supported.
CompleteUpdate()
Installs a downloaded Flexible update. On a device Google Play restarts the app.
event CheckCompleted (UpdateInfo)
The result of a check.
event StateChanged (InstallState)
Progress and status of the running update.
event Declined (UpdateMode)
The player dismissed the Play update dialog.
event Failed (UpdateError)
A check, a flow or an install failed.
LastInfo, State
The last check result and the last install state.
CurrentMode
The mode of the running or most recent flow, kept across app restarts.
IsChecking, IsFlowActive
Whether a check or a flow is running.
IsSupported, IsSimulated
Whether this build has a backend (Google Play or the simulator), and whether it is the simulator.
Simulator
The SimulatorSettings the simulator plays back. InAppUpdater assigns its own.

UpdateInfo and InstallState

UpdateInfo.Availability
Unknown, NotAvailable, Available, or InProgress (a flow started earlier is still running). IsUpdateAvailable is true for Available.
UpdateInfo.AvailableVersionCode
The version code of the update.
UpdateInfo.Priority
0-5, from the Play Developer API.
UpdateInfo.StalenessDays
Days since Google Play learned about the update, or null if unknown.
UpdateInfo.FlexibleAllowed, ImmediateAllowed, IsAllowed(mode)
Which modes Google Play allows for this update.
UpdateInfo.InstallStatus, BytesDownloaded, TotalBytesToDownload
The state of a download in progress.
InstallState.Status
Pending, Downloading, Downloaded, Installing, Installed, Failed or Canceled.
InstallState.Progress
0 to 1. Google Play sometimes reports a total of 0, so it falls back to the status.
InstallState.BytesDownloaded, TotalBytesToDownload
Byte counts of the download.

UpdateError

The values cover Google's InstallErrorCode: ApiNotAvailable, InvalidRequest, UpdateUnavailable, UpdateNotAllowed, DownloadNotPresent, UpdateInProgress, InternalError, UpdateFailed, PlayStoreNotFound, AppNotOwned and more. UserCanceled is reported through Declined, not Failed. NotSupported is the package's own: the platform or build has no backend.

No platform checks needed

On iOS and other platforms every call raises Failed with UpdateError.NotSupported and does nothing else, so the same code ships everywhere.

Editor simulator

In the Editor every call goes to a simulator that behaves like Google Play. Configure it under Simulator on the InAppUpdater; changes apply live in Play Mode. While playing, the Inspector also shows the live state (backend, last check, install state, running flow) and buttons for Check, Flexible, Immediate and Install.

Availability, Available Version Code
What a check reports. In Progress tests resuming an update after an app restart.
Priority, Staleness Days
The release's priority (0-5) and age in days (-1 = unknown), for testing the policy.
Flexible Allowed, Immediate Allowed
Which modes Google Play allows.
User Choice
How the simulated player answers the update dialog: Ask shows a stand-in dialog in the Game view; Accept or Decline answer by themselves.
Check Error
An error every check returns. None = success.
Fail Download
Fails the download half way.
Network Delay, Download Seconds, Download Size Mb
Timing and size of the simulated check and download.
Use In Development Builds
Runs the simulator on a device too, in Development builds only: handy for testing your UI without a Play release. Release builds always use Google Play.

Testing on a device

Real updates only appear for an app installed from Google Play.

  1. Install from Play Upload a build to an internal testing track and install it from Google Play on the device, as a tester.
  2. Publish a newer build Upload a second build with a higher version code.
  3. Let Play notice Open the Play Store on the device so it sees the new version, then launch your installed build.

Internal app sharing is faster: install one version through a sharing link, then open the link of a higher version code without installing it. Google Play caches what it knows, so clearing the Play Store's storage can help.

How it works

The package handles the parts of Google's API that are easy to get wrong:

  • Resuming. If the app was closed during an update, the next check reports InProgress and the flow is resumed in the same mode, as Google requires.
  • Background downloads. A Flexible update that finished while the app was closed or in the background is found by the next check and reported as Downloaded.
  • Single-use info. Google lets each update info start one flow only; the package fetches fresh info when needed.
  • One flow at a time. StartUpdate refuses a second flow while one runs.
  • Not nagging. After a Flexible decline, InAppUpdater waits Flexible Reprompt Days before offering it again.
  • Domain reload off. Static state is reset on entering Play Mode, so Enter Play Mode Options works.

Assemblies

Hardartcore.InAppUpdates
The runtime: InAppUpdate, InAppUpdater, the types, the Google Play backend (Google's Java API through JNI) and the simulator. No references to other assemblies and no scripting defines.
Hardartcore.InAppUpdates.Editor
The Inspector (live state, test buttons, the EDM installer), InAppUpdatesDependencies.xml, and the R8 keep rules for Android builds.
Hardartcore.InAppUpdates.Tests
PlayMode tests of the update policy and the simulator flows.

Folder layout

Assets/HardArtcore/In-App Updates/
├── Documentation.html          this documentation
├── Runtime/                    InAppUpdate, InAppUpdater, types, Play backend, simulator
├── Editor/                     Inspector and the Google plugin installer
├── Prefabs/InAppUpdater.prefab the drop-in component
├── Tests/                      policy and simulator tests
└── Demo/                       DemoScene, its scripts, Nunito font and icons

Upgrading from 1.x

Version 2 replaces 1.x (UpdateManager.AndroidUpdateManager, AutoUpdater) entirely.

  1. Delete the old files Importing doesn't delete them. Under Assets/hardartcore/In-App Updates/, delete Plugins/ (the old update-release.aar breaks the Android build against the new Play libraries), Scripts/AutoUpdater.cs and Prefabs/AutoUpdateManager.prefab.
  2. Clean the manifest Remove the old Game Package Registry entry from Packages/manifest.json (Google shut that registry down), then add the External Dependency Manager. Google's com.google.play.* Unity packages are no longer needed: remove them unless other plugins use them.
  3. Review the components Existing AndroidUpdateManager components become InAppUpdater automatically (same script GUID). An AutoUpdater becomes an InAppUpdater with Auto Start and Auto Install ticked.
1.x2.0
OnUpdateAvailableInAppUpdate.CheckCompleted
OnUpdateDownloading, OnStateUpdate, OnUpdateDownloadedInAppUpdate.StateChanged
OnErrorReceivedInAppUpdate.Failed
CheckForAnUpdate()InAppUpdate.Check()
StartUpdate()InAppUpdate.StartUpdate(mode)
CompleteUpdate()InAppUpdate.CompleteUpdate()

Troubleshooting

No update is ever found on the device
The app must be installed from Google Play, and Google Play must know about a higher version code. See Testing on a device; clearing the Play Store's storage refreshes its cache.
"com.google.android.play:app-update is missing from the build" on Android
The Android dependencies weren't resolved: Assets > External Dependency Manager > Android Resolver > Force Resolve, then build again (see Android dependencies). With Minify on and your own Gradle setup, check that the keep rules reached the build.
Failed: AppNotOwned or PlayStoreNotFound
The build was sideloaded, or the device has no Play Store. Install from a Play track or internal app sharing.
Failed: ApiNotAvailable
The Play Store on the device is too old, or the device doesn't support in-app updates.
Duplicate classes when building
Left-over 1.x files: delete Plugins/update-release.aar (see Upgrading from 1.x), then resolve with External Dependency Manager.
The dialog shows only once
By design: Auto Start offers an update once per session, and a declined Flexible update waits Flexible Reprompt Days. Call StartFlexibleUpdate() from a button to offer it again.

For device issues, attach Logcat output filtered by Unity and PlayCore when you write to support.

Third-party notices

The demo includes two third-party components. Google's Play In-App Updates library is not included: the External Dependency Manager downloads it from Google's Maven repository, under Google's own terms. Everything else in the package is original work by HardArtcore.

ComponentLocationLicense
Nunito font
Copyright 2014 The Nunito Project Authors (github.com/googlefonts/nunito)
Demo/UI/Fonts/SIL Open Font License 1.1
Material Symbols icons
Copyright Google LLC (github.com/google/material-design-icons)
Demo/UI/Icons/Apache License 2.0

The full licence texts ship in the package's Documentation.html, and online: SIL Open Font License 1.1, Apache License 2.0.

Release notes

2.0.0

  • Rewritten on Google's Play In-App Updates library (com.google.android.play:app-update 2.1.0), declared for the External Dependency Manager: no Google Unity packages to install.
  • InAppUpdater component with an update policy: Immediate by priority or staleness, Flexible otherwise, no re-prompting for a few days after a decline.
  • Static InAppUpdate API with C# events on the main thread; a safe no-op on iOS and other platforms.
  • Editor simulator with a stand-in Play dialog, live Inspector state and test buttons, and an option for Development builds.
  • Resumes interrupted updates and finds downloads that finished in the background.
  • New demo scene, assembly definitions and automated tests.

1.x

  • AndroidUpdateManager and AutoUpdater on the Play Core library.

Need help?

Questions, bug reports or feature requests: I usually reply within a day.

support@hardartcore.com

Last updated November 9, 2026.