3

I'm new to .NET Maui. I would like to notify users who have an old version of my app that a new version is available on the store. I managed to do it when the user click on the Login Button with these few lines of code:


[RelayCommand]

async Task Login()
{

    var isLatest = await CrossLatestVersion.Current.IsUsingLatestVersion();

    if (!isLatest)
    {
        await Application.Current.MainPage.DisplayAlert("Update", 
              "New version available", "OK");
        await CrossLatestVersion.Current.OpenAppInStore();
    }
    else
    {...}
}

But I would like to notify the user before the LoginPage appears. I tried to do it in the method

protected override async void OnAppearing()

of the LoginPage but it doesn't work, it seems that the call

CrossLatestVersion.Current.IsUsingLatestVersion();

dies, without errors and otherwise unanswered...

Any suggestions? Thank you all in advance!!

Marco
  • 56,740
  • 14
  • 129
  • 152
Davide
  • 31
  • 2
  • "dies, without errors and otherwise unanswered..." No. Code does not die. Debug your communication. Do not write 10 pages of code, and then say "nothing works". Make a simple request that will return your version. Use fiddler or some other http agent to see what you are sending and receiving. – H.A.H. Jun 15 '23 at 10:28
  • Why dont you try to get the latest version available by code instead of using that nugget package, Its just a small piece of code to get the version number from stores. Using that you can easily debug the code & even handle if there are any unexpected exceptions – Blu Jun 15 '23 at 16:08
  • On my experience accessing Google Play/Apple AppStore pages directly and parsing them is not a reliable way as they do not provide API for version check. They can redesign their pages (and did that before) and your code will stop working. Use your own mechanisms on your premises to have a reliable implementation. – Rafael Jun 16 '23 at 11:03
  • @Rafael I can understand your concern, we have gone through similar discussions. But that's the case only for Google play store, Apple store have a dedicare api for it which won't be changed. And even for Google play store, if they redesign the page, their will be less possibility that they won't include the version number in UI, possibility like negligible. Thats the reason we use regex to iterate the page and have the version number from any kind of page, so if the UI changes there is no effect on our App – Blu Jun 19 '23 at 07:30
  • You can try to put it in the `OnStart` method of the app.cs. Refer to my old answer about [Problem with using await in App.xaml.cs constructor](https://stackoverflow.com/a/75937447/17455524). – Liyun Zhang - MSFT Jun 19 '23 at 09:52

2 Answers2

3

Inref to the above comment, here is the code snippet i use to get the latest version of App from the stores. It is the same technique used in the nugget package you are using. Try to get into the GitHub of the nugget you using, its similar.

public async Task<string> GetLatestVersionAvailableOnStores()
        {
            string remoteVersion = "";
            string bundleID = YOUR_BUNDLE_ID;
            bool IsAndroid = DeviceInfo.Current.Platform == DevicePlatform.Android;

            var url = IsAndroid ?
                "https://play.google.com/store/apps/details?id=" + bundleID :
                "http://itunes.apple.com/lookup?bundleId=" + bundleID;

            using (HttpClient httpClient = new HttpClient())
            {
                string raw = await httpClient.GetStringAsync(new Uri(url));

                if (IsAndroid)
                {
                    var versionMatch = Regex.Match(raw, @"\[\[""\d+.\d+.\d+""\]\]"); //look for pattern [["X.Y.Z"]]
                    if (versionMatch.Groups.Count == 1)
                    {
                        var versionMatchGroup = versionMatch.Groups[0];
                        if (versionMatchGroup.Success)
                            remoteVersion = versionMatch.Value.Replace("[", "").Replace("]", "").Replace("\"", "");
                    }
                }
                else
                {
                    JObject obj = JObject.Parse(raw);
                    if (obj != null)
                        remoteVersion = obj["results"]?[0]?["version"]?.ToString() ?? "9.9.9";
                }
            }

            return remoteVersion;
        }

You can have the Application version by using any static variable in your project. And then you can perform the comparison between both the strings. I used natural comparison to check which is the latest version.

iOS:

            SharedProjectClass.ApplicationVersion = NSBundle.MainBundle.InfoDictionary["CFBundlePublicVersionString"].ToString();

Android:

            SharedPRojectClass.ApplicationVersion = PackageManager.GetPackageInfo(PackageName, 0).VersionName;

Common Project:
    public static class SharedProjectCLass
    {
        /// <summary>
        /// The application version.
        /// </summary>
        public static string ApplicationVersion;
     }
async Task CheckVersion()
{
            var currentVersion = SharedProjectClass.ApplicationBuildNumber;

            var remoteVersion = await GetLatestVersionAvailableOnStores();

            // Function to natural compare the App version of format d.d.d
            Func<string, string, bool> CompareNaturalStringsOfAppVersion = (currentVersion, remoteVersion) =>
            {
                string[] components1 = currentVersion.Split('.');
                string[] components2 = remoteVersion.Split('.');

                for (int i = 0; i < components1.Length; i++)
                {
                    int value1 = Convert.ToInt32(components1[i]);
                    int value2 = Convert.ToInt32(components2[i]);

                    if (value1 < value2)
                    {                  
                        return true; // string1 is lower
                    }
                    else if (value1 > value2)
                    {
                        return false; // string2 is lower
                    }
                }

                return false; // both strings are equal
            };

            bool needToUpdateApp = CompareNaturalStringsOfAppVersion(currentVersion, remoteVersion);

            if (needToUpdateApp)
            {
                 //Show aleart pop-up to user saying new version is available.
            }
}
Blu
  • 821
  • 4
  • 17
0

Add to question the complete code of your failed attempt in OnAppearing.

If LoginPage was the first page that displays in your app, then you definitely don't want to prevent OnAppearing from returning, while you check the version - it is important to have your app display something ASAP on startup.

I make a "StartupPage" with app/company logo + "Loading...". This displays while any initialization logic is running.

The technique is to run initialization in the background. When complete, go to an appropriate page. In your case, go to one of two pages:

  • If up-to-date, go to Login page.
  • If attempt to get store version fails, also go to Login page!
  • If there is a newer version, go to page that informs user of new version, with two buttons. One button goes to App Store, the other button goes to Login page.

StartupPage's OnAppearing:

public partial class StartupPage : ContentPage
{
    protected override void OnAppearing()
    {
        base.OnAppearing();

        // Start background work. IMPORTANT: DO NOT "await" Task.Run itself;
        // let `OnAppearing` return before this work is done.
        Task.Run( async () => await Initialization() );
    }

    // Make sure this is run on a background thread (via Task.Run).
    // REASON: Don't want to delay StartupPage appearing.
    private async Task Initialization()
    {
        // true if there is an update available.
        // false if no update.
        // false if time out or error while checking app version.
        bool appUpdateExists = false;

        try
        {
            // ... your code to check app version
            if (...)
            {
                appUpdateExists = true;
            }
        }
        catch (Exception ex)
        {   // Any failure; assume there is no update, so proceeds to login.
            appUpdateExists = false;
        }

        // Get back to MainThread, before switching to new page.
        MainThread.BeginInvokeOnMainThread( async () =>
        {
            if (appUpdateExists)
                 ... // to UpdateExistsPage.
            else
                 ... // to LoginPage.
        }
    }
}
ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196