Last active
June 2, 2023 03:26
-
-
Save binouze/a574eee22e08b6ec076cfe594897f191 to your computer and use it in GitHub Desktop.
Simplest implementation of Android in-app updates for Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
public sealed class AutoUpdater : MonoBehaviour | |
{ | |
private const string AndroidClass = "com.lagoonsoft.AutoUpdater"; | |
#region static Singleton | |
private static AutoUpdater _instance; | |
private static AutoUpdater GetInstance() | |
{ | |
if( _instance == null ) | |
{ | |
_instance = new GameObject("AutoUpdater").AddComponent<AutoUpdater> (); | |
DontDestroyOnLoad (_instance.gameObject); | |
} | |
return _instance; | |
} | |
#endregion | |
#region LISTENERS | |
public static Action OnInstalReady; | |
public static Action OnInstalAvailable; | |
public void OnInstallReady( string useless ) | |
{ | |
Debug.Log("[AutoUpdater] OnInstallReady" ); | |
InstallAvailable = true; | |
OnInstalReady?.Invoke(); | |
} | |
public void OnUpdateStatus( string versioncode ) | |
{ | |
Debug.Log("[AutoUpdater] OnUpdateStatus " + versioncode ); | |
if( versioncode != "-1" && versioncode != "-2" ) | |
{ | |
UpdateAvailable = true; | |
OnInstalAvailable?.Invoke(); | |
} | |
} | |
public void OnUpdating( string useless ) | |
{ | |
Debug.Log("[AutoUpdater] OnUpdating" ); | |
} | |
public void OnDownloadProgress( string progress ) | |
{ | |
Debug.Log("[AutoUpdater] OnDownloadProgress " + progress ); | |
} | |
public static bool InstallAvailable { get; private set; } | |
public static bool UpdateAvailable { get; private set; } | |
#endregion | |
/// <summary> | |
/// Start check for updates | |
/// </summary> | |
public static void Init() | |
{ | |
GetInstance(); | |
InstallAvailable = false; | |
UpdateAvailable = false; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using( var cls = new AndroidJavaClass( AndroidClass ) ) { cls.CallStatic( "Init" ); } | |
#endif | |
} | |
/// <summary> | |
/// Start immediate update | |
/// </summary> | |
public static bool StartUpdateImmediate() | |
{ | |
InstallAvailable = false; | |
UpdateAvailable = false; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using( var cls = new AndroidJavaClass(AndroidClass) ) { return cls.CallStatic<bool>("ApplyImmediateUpdate"); } | |
#else | |
return false; | |
#endif | |
} | |
/// <summary> | |
/// Start felxible update | |
/// </summary> | |
public static bool ProposeUpdateFlexible() | |
{ | |
InstallAvailable = false; | |
UpdateAvailable = false; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using( var cls = new AndroidJavaClass(AndroidClass) ) { return cls.CallStatic<bool>("ApplyFlexUpdate"); } | |
#else | |
return false; | |
#endif | |
} | |
/// <summary> | |
/// Start updating the app that has finish download | |
/// </summary> | |
public static void CompleteUpdate() | |
{ | |
InstallAvailable = false; | |
UpdateAvailable = false; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using( var cls = new AndroidJavaClass(AndroidClass) ) { cls.CallStatic("CompleteUpdate"); } | |
#endif | |
} | |
/// <summary> | |
/// Update download status on app resume | |
/// </summary> | |
public static void OnResume() | |
{ | |
InstallAvailable = false; | |
UpdateAvailable = false; | |
#if UNITY_ANDROID && !UNITY_EDITOR | |
using( var cls = new AndroidJavaClass(AndroidClass) ) { cls.CallStatic("OnResume"); } | |
#endif | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.lagoonsoft; | |
import android.content.IntentSender; | |
import android.util.Log; | |
import com.google.android.material.snackbar.Snackbar; | |
import com.google.android.play.core.appupdate.AppUpdateInfo; | |
import com.google.android.play.core.appupdate.AppUpdateManager; | |
import com.google.android.play.core.appupdate.AppUpdateManagerFactory; | |
import com.google.android.play.core.install.InstallState; | |
import com.google.android.play.core.install.model.AppUpdateType; | |
import com.google.android.play.core.install.model.InstallStatus; | |
import com.google.android.play.core.install.model.UpdateAvailability; | |
import com.google.android.play.core.tasks.Task; | |
import com.unity3d.player.UnityPlayer; | |
import java.util.Random; | |
public class AutoUpdater | |
{ | |
private static final String TAG = "AutoUpdater"; | |
private static final String ON_INSTALL_READY = "OnInstallReady"; | |
private static final String ON_UPDATE_STATUS = "OnUpdateStatus"; | |
private static final String ON_UPDATING = "OnUpdating"; | |
private static final String ON_DOWNLOADING = "OnDownloadProgress"; | |
private static AutoUpdater _instance; | |
private AppUpdateManager appUpdateManager; | |
private AppUpdateInfo appUpdateInfo; | |
private final int MY_REQUEST_CODE; | |
private boolean hasFlexUpdateAvailable() | |
{ | |
return appUpdateInfo != null && appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE); | |
} | |
private boolean hasImmediateUpdateAvailable() | |
{ | |
return appUpdateInfo != null && appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE); | |
} | |
private int getAvailableVersionCode() | |
{ | |
return appUpdateInfo != null && appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ? appUpdateInfo.availableVersionCode() : -1; | |
} | |
/** | |
* singleton class | |
*/ | |
private AutoUpdater() | |
{ | |
Random rand = new Random(); | |
MY_REQUEST_CODE = rand.nextInt(); | |
} | |
/** | |
* Check if updates are available | |
*/ | |
private void _init() | |
{ | |
log("_init"); | |
// Creates instance of the manager if not exists. | |
if( appUpdateManager == null ) | |
appUpdateManager = AppUpdateManagerFactory.create( UnityPlayer.currentActivity ); | |
// Returns an intent object that you use to check for an update. | |
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo(); | |
// Checks that the platform will allow the specified type of update. | |
appUpdateInfoTask.addOnSuccessListener( updateInfo -> | |
{ | |
log(" -> OnSuccessListener"); | |
appUpdateInfo = updateInfo; | |
// Update waiting | |
if( appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED ) | |
{ | |
log(" -> DOWNLOADED: " + getAvailableVersionCode() ); | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_INSTALL_READY, "0" ); | |
} | |
// Update available | |
else if( appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ) | |
{ | |
log(" -> UPDATE_AVAILABLE: " + getAvailableVersionCode() ); | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_UPDATE_STATUS, ""+appUpdateInfo.availableVersionCode() ); | |
} | |
// Immediate update in progress | |
else if( appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS ) | |
{ | |
log(" -> DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS: " + getAvailableVersionCode() ); | |
// If an in-app update is already running, resume the update. | |
_applyImmediateUpdate(); | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_UPDATING, "0" ); | |
} | |
// none | |
else | |
{ | |
log(" -> NO UPDATES" ); | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_UPDATE_STATUS, "-1" ); | |
} | |
}); | |
// check for errors | |
appUpdateInfoTask.addOnFailureListener( fail -> | |
{ | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_UPDATE_STATUS, "-2" ); | |
}); | |
} | |
/** | |
* Singleton | |
* @return AutoUpdater | |
*/ | |
private static AutoUpdater getInstance() | |
{ | |
if( _instance == null ) | |
{ | |
_instance = new AutoUpdater(); | |
} | |
return _instance; | |
} | |
private void _onStateUpdate( InstallState state ) | |
{ | |
log(" -> _onStateUpdate : " + state.installStatus() ); | |
if( state.installStatus() == InstallStatus.DOWNLOADED ) | |
{ | |
appUpdateManager.unregisterListener( this::_onStateUpdate ); | |
_init(); | |
} | |
else if( state.installStatus() == InstallStatus.DOWNLOADING ) | |
{ | |
long bytesDownloaded = state.bytesDownloaded(); | |
long totalBytesToDownload = state.totalBytesToDownload(); | |
long progress = bytesDownloaded / totalBytesToDownload; | |
UnityPlayer.UnitySendMessage("AutoUpdater", ON_DOWNLOADING, String.format("Value: {0:P2}.", progress) ); | |
} | |
} | |
private void _onResume() | |
{ | |
_init(); | |
} | |
private void _completeUpdate() | |
{ | |
log(" -> _completeUpdate" ); | |
if( appUpdateManager == null ) | |
{ | |
log(" -> impossible appUpdateManager == null" ); | |
} | |
else if( appUpdateInfo == null ) | |
{ | |
log(" -> impossible appUpdateInfo == null" ); | |
return; | |
} | |
else if( appUpdateInfo.installStatus() != InstallStatus.DOWNLOADED ) | |
{ | |
log(" -> impossible appUpdateInfo.installStatus() != InstallStatus.DOWNLOADED" ); | |
return; | |
} | |
appUpdateManager.completeUpdate(); | |
} | |
private boolean _applyFlexUpdate() | |
{ | |
log(" -> _applyFlexUpdate " + hasFlexUpdateAvailable() ); | |
if( hasFlexUpdateAvailable() ) | |
{ | |
try | |
{ | |
appUpdateManager.registerListener( this::_onStateUpdate ); | |
appUpdateManager.startUpdateFlowForResult( | |
// Pass the intent that is returned by 'getAppUpdateInfo()'. | |
appUpdateInfo, | |
// Or 'AppUpdateType.IMMEDIATE' for immediate updates. | |
AppUpdateType.FLEXIBLE, | |
// The current activity making the update request. | |
UnityPlayer.currentActivity, | |
// Include a request code to later monitor this update request. | |
MY_REQUEST_CODE); | |
return true; | |
} | |
catch( IntentSender.SendIntentException e ) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
return false; | |
} | |
private boolean _applyImmediateUpdate() | |
{ | |
log(" -> _applyImmediateUpdate " + hasImmediateUpdateAvailable() ); | |
if( hasImmediateUpdateAvailable() ) | |
{ | |
try | |
{ | |
appUpdateManager.registerListener( this::_onStateUpdate ); | |
appUpdateManager.startUpdateFlowForResult( | |
// Pass the intent that is returned by 'getAppUpdateInfo()'. | |
appUpdateInfo, | |
// Or 'AppUpdateType.FLEXIBLE' for flexible updates. | |
AppUpdateType.IMMEDIATE, | |
// The current activity making the update request. | |
UnityPlayer.currentActivity, | |
// Include a request code to later monitor this update request. | |
MY_REQUEST_CODE); | |
return true; | |
} | |
catch( IntentSender.SendIntentException e ) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
return false; | |
} | |
// ----------------------------------------------------------------------------------------------------------------| | |
// -- STATICS UNITY FUNCTIONS -- ----------------------------------------------------------------------------------| | |
// ----------------------------------------------------------------------------------------------------------------| | |
/** | |
* Check if updates are available | |
*/ | |
public static void Init() | |
{ | |
getInstance()._init(); | |
} | |
/** | |
* Launch FlexibleUpdate | |
* @return boolean | |
*/ | |
public static boolean ApplyFlexUpdate() | |
{ | |
return getInstance()._applyFlexUpdate(); | |
} | |
/** | |
* Launch ImmediateUpdate | |
* @return boolean | |
*/ | |
public static boolean ApplyImmediateUpdate() | |
{ | |
return getInstance()._applyImmediateUpdate(); | |
} | |
/** | |
* Start installing downloaded flexible update | |
*/ | |
public static void CompleteUpdate() | |
{ | |
getInstance()._completeUpdate(); | |
} | |
/** | |
* returns true if a FlexibleUpdate is available | |
*/ | |
public static boolean FlexUpdateDisponible() | |
{ | |
return getInstance().hasFlexUpdateAvailable(); | |
} | |
/** | |
* returns true if an ImmediateUpdate is available | |
*/ | |
public static boolean ImmediateUpdateDisponible() | |
{ | |
return getInstance().hasImmediateUpdateAvailable(); | |
} | |
/** | |
* returns the version number of the available update | |
*/ | |
public static int GetVersionDisponible() | |
{ | |
return getInstance().getAvailableVersionCode(); | |
} | |
/** | |
* relaunch checks on app resumes | |
*/ | |
public static void OnResume() | |
{ | |
getInstance()._onResume(); | |
} | |
/** | |
* logs | |
* @param msg String | |
*/ | |
private void log( String msg ) | |
{ | |
Log.d(TAG, msg); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- ADD this in an Editor folder --> | |
<dependencies> | |
<androidPackages> | |
<androidPackage spec="com.google.android.material:material:1.0.+"/> | |
<androidPackage spec="com.google.android.play:core:1.6.+"/> | |
</androidPackages> | |
</dependencies> |
Hi, you can create a folder AutoUpdater inside Assets/Plugins folder and put all files inside.
This Gist is a little old, Google published their own implementation of InAppUpdates for Unity, I think it's better to use this one ;)
https://developer.android.com/guide/playcore/in-app-updates/unity
yes, but I couldn't find complete E2E codes. and goggling result returns some incomplete solutions.
can you help me?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
could you please say more detail, like where should exactly AutoUpdater.java file and can we create an editor folder under asset/scripts folder and put AutoUpdaterDependencies.xml inside of it?