How to force users to update new version of my iOS app

How are people handling making a feature that I can force users to update the app if I release a new version for iOS/Android?

Have a server where you have the latest version stored and at boot check it. Maybe the app stores got proprietary support for it as well

Agreed! Also, Unity has such a service too with Remote Config and could be used for this very purpose Remote Config: Live Game Content Updates Software | Unity. As mentioned, you keep a copy of the latest version number on the server and compare it to the app version during app launch.

1 Like

Sure, I was hoping for some cool feature in it.

On my business apps in Xamarin, I always do a query to the store to get the current version number and if the current app is less, I force them to to go to the store. Trying to limit the overhead.

For example:
App Name

https://developer.apple.com/library/archive/qa/qa1633/_index.html

So how much does that remote config cost per month, it looks awesome!

It’s a free service. It’s part of the Unity Gaming Services offering and does require a credit card signup https://unity.com/solutions/gaming-services/pricing

I found this works better, substitute your app name. This way I don’t have to control resources for checking. Just throw the OpenUrl into a button. If your using 3 decimals in the build, just parse it out instead of using a double.

iOS:

 StartCoroutine(GetRequest("https://itunes.apple.com/lookup?bundleId=com.apple.iBooks", (result) =>
           {
               try
               {
                   var jsonResult = JObject.Parse(result);
                 
                   if (Convert.ToInt32(jsonResult["resultCount"]) > 0)
                   {
                       Debug.Log("Result count is > 1");

                       double storeVersion = Convert.ToDouble( jsonResult["results"][0]["version"]);
                       Debug.Log($"Store version is {storeVersion}");

                       Debug.Log($"App version is {Application.version}");

                       if (Convert.ToDouble(Application.version) < storeVersion)
                       {
                           Application.OpenURL("https://itunes.apple.com/us/app/apple-store/id364709193");
                       }
                   } else
                   {
                       Debug.LogError("The app was not found in the store");
                   }
               }
               catch (Exception ex)
               {
                   Debug.Log($"Exception {ex}");
               }
           }));

Android involves the same process, but you need to regex the api call

1 Like

If your game has and needs a server, by all means send a version and block non-matching versions.

However I would NOT recommend adding ANYTHING like the above to your code, as most mobile users just auto-update. Various sources report anywhere from 80% to 99% updates within the first week. Do you really want to support that last 1%?

All of the above code will fail the moment any of the text your are scraping changes. There will be no way for you to identify that they have failed because the above code has no reporting mechanisms in it, and not only that it has Pokemon exception catching which as we know does absolutely NOBODY any good whatseover.

catch (Exception ex)
{
   Debug.Log("Why do people catch all exceptions? They're not playing pokemon...");
}
1 Like

Good point. I was worried there may be a bug or glitch being taken advantage of during a leaderboard score. I could match that.

I do have a question as I don’t want to manage a server. Is there a way to block leaderboard score updates based on a current version. This is for iOS and my only concern was apple leaderboards being updated if a user was taking advantage of a bug/glitch?

Check the Apple docs but AFAIK the answer would be no.

You could perhaps just make a new leaderboard and stop using the old leaderboard sku … it would still be there but nobody could score against it.

strongly recommend HybridCLR.

HybridCLR is a almost perfect full-platform native c# hot update solution for Unity with complete features, zero cost, high performance, and low memory**.

HybridCLR expands the code of il2cpp, making it change from pure AOT runtime to AOT+Interpreter hybrid runtime, and then natively supports dynamic loading of assembly , so that the games packaged based on il2cpp backend can be executed not only on the Android platform, but also on IOS, Consoles and other platforms that limit JIT efficiently in AOT+interpreter hybrid mode, completely supporting hot updates from the bottom layer.

HybridCLR not only supports the traditional fully interpreted execution mode, but also pioneered the Differential Hybrid Execution(DHE) differential hybrid execution technology. That is, you can add, delete, or modify the AOT dll at will, and intelligently make the changed or newly added classes and functions run in interpreter mode, but the unchanged classes and functions run in AOT mode, so that the running performance of the hot-updated game logic basically reaches the original AOT level.

About to implement this:

Hello everyone here is the code for updating android and iOS both

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

#if UNITY_ANDROID
using Google.Play.AppUpdate;
using Google.Play.Common;
#endif

public class Screen_AppUpdate : BASE_SCREEN
{
[System.Serializable]
public class APPUPDATE_IOS_RESULT
{
public int trackId;
public string version;
}
[System.Serializable]
public class APPUPDATE_IOS
{
public int resultCount;
public List<APPUPDATE_IOS_RESULT> results;
}

[Space]
[SerializeField] Button btn_Install;

#if UNITY_ANDROID
AppUpdateManager appUpdateManager;
AppUpdateInfo appUpdateInfoResult_Android;
#endif

APPUPDATE_IOS appUpdateInfoResult_IOS;


private void Start()
{
    btn_Install.onClick.AddListener(OnClick_Install);
}


public override void Show()
{
    Loading.Instance.Show("Setting Scene");

    Loading.Instance.Update_LoadingText("Checking updates");

    CheckForUpdate(isUpdateAvailable =>
    {
        Debug.Log("Update Available : " + isUpdateAvailable);

        if (isUpdateAvailable)
        {
            Loading.Instance.Hide();
            base.Show();
        }
        else
            AddressableManager.Instance.CheckForUpdates();
    });
}


#region Checking for updates
private void CheckForUpdate(Action<bool> updateAvailableCallback)
{

#if UNITY_ANDROID && !UNITY_EDITOR
StartCoroutine(CheckForUpdate_Android(updateAvailableCallback));
#elif UNITY_IOS && !UNITY_EDITOR
StartCoroutine(CheckForUpdate_IOS(updateAvailableCallback));
#else
if (updateAvailableCallback != null)
updateAvailableCallback(false);
#endif
}

#if UNITY_ANDROID
private IEnumerator CheckForUpdate_Android(Action updateAvailableCallback)
{
appUpdateManager = new AppUpdateManager();
PlayAsyncOperation<AppUpdateInfo, AppUpdateErrorCode> appUpdateInfoOperation = appUpdateManager.GetAppUpdateInfo();

    yield return appUpdateInfoOperation;

    if (appUpdateInfoOperation.IsSuccessful)
    {
        appUpdateInfoResult_Android = appUpdateInfoOperation.GetResult();
        Debug.Log(appUpdateInfoResult_Android.UpdateAvailability);

        if (updateAvailableCallback != null)
            updateAvailableCallback(appUpdateInfoResult_Android.UpdateAvailability == UpdateAvailability.UpdateAvailable);
    }
    else
    {
        Debug.Log("Check for updated failed");
        if (updateAvailableCallback != null)
            updateAvailableCallback(false);
    }
}

#endif
private IEnumerator CheckForUpdate_IOS(Action updateAvailableCallback)
{
string url = “http://itunes.apple.com/br/lookup?bundleId=” + Application.identifier;
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
yield return request.SendWebRequest();

        while (!request.isDone)
            yield return null;

        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log(request.downloadHandler.text);

            appUpdateInfoResult_IOS = JsonUtility.FromJson<APPUPDATE_IOS>(request.downloadHandler.text);
            if (appUpdateInfoResult_IOS.resultCount == 0)
            {
                if (updateAvailableCallback != null)
                    updateAvailableCallback(false);
            }
            else
            {
                if (updateAvailableCallback != null)
                    updateAvailableCallback(appUpdateInfoResult_IOS.results[0].version != Application.version);
            }
        }
        else
        {
            if (updateAvailableCallback != null)
                updateAvailableCallback(false);
        }
    }
}
#endregion


#region Install update
private void OnClick_Install()
{

#if UNITY_ANDROID && !UNITY_EDITOR
StartCoroutine(InstallUpdate_Android());
#elif UNITY_IOS && !UNITY_EDITOR
InstallUpdate_IOS();
#else
Debug.Log(“Cannot update for this platform”);
#endif
}

#if UNITY_ANDROID
IEnumerator InstallUpdate_Android()
{
var appUpdateOptions = AppUpdateOptions.ImmediateAppUpdateOptions();

    var startUpdateRequest = appUpdateManager.StartUpdate(appUpdateInfoResult_Android, appUpdateOptions);
    yield return startUpdateRequest;

    // If the update completes successfully, then the app restarts and this line
    // is never reached. If this line is reached, then handle the failure (for
    // example, by logging result.Error or by displaying a message to the user)
    Debug.Log("App Update failed");
    base.Show();
}

#endif
void InstallUpdate_IOS()
{
Application.OpenURL(string.Format(“iTunes - Apple”, appUpdateInfoResult_IOS.results[0].trackId));
}
#endregion
}