Partager via


Migrer des données de suivi de version d’une application Xamarin.Forms vers une application .NET MAUI

Xamarin.Essentials et l’interface utilisateur de l’application multiplateforme .NET (.NET MAUI) disposent d’une classe VersionTracking qui vous permet de vérifier la version et les numéros de build de l’application, ainsi que des informations supplémentaires, comme si c’était la première fois que l’application était lancée. Toutefois, dans Xamarin.Essentials, les données de suivi des versions sont stockées dans un conteneur de préférences spécifiques à la plateforme avec un nom de {your-app-package-id}.xamarinessentials.versiontracking, tandis que dans .NET MAUI, les données de suivi de version sont stockées dans un conteneur de préférences spécifiques à la plateforme avec un nom de {your-app-package-id}.microsoft.maui.essentials.versiontracking.

Lors de la migration d’une application Xamarin.Forms qui utilise la classe VersionTracking vers .NET MAUI, vous devez gérer cette différence de nommage de conteneur de préférences pour fournir aux utilisateurs une expérience de mise à niveau fluide. Cet article explique comment utiliser la classe LegacyVersionTracking et les classes d’assistance pour gérer le conteneur de préférences. La classe LegacyVersionTracking permet à votre application .NET MAUI sur Android, iOS et Windows de lire des données de suivi de version qui ont été créées avec une version précédente de votre application Xamarin.Forms.

Important

Pour que la classe LegacyVersionTracking fonctionne correctement, votre application .NET MAUI doit avoir un numéro de version supérieur au numéro de version de votre application Xamarin.Forms. Le numéro de version peut être défini dans le fichier projet de votre application .NET MAUI avec les propriétés de build $(ApplicationVersion) et $(ApplicationDisplayVersion).

Pour plus d’informations sur la classe VersionTracking dans Xamarin.Essentials, consultez Xamarin.Essentials : Suivi des versions. Pour plus d’informations sur la classe VersionTracking dans .NET MAUI, consultez le suivi des versions.

Accéder aux données de suivi des versions héritées

Le code suivant montre la classe LegacyVersionTracking qui permet d’accéder aux données de suivi des versions créées par votre application Xamarin.Forms :

Remarque

Pour utiliser ce code, ajoutez-le à une classe nommée LegacyVersionTracking dans votre projet d’application .NET MAUI.

namespace MigrationHelpers;

public static class LegacyVersionTracking
{
    const string versionsKey = "VersionTracking.Versions";
    const string buildsKey = "VersionTracking.Builds";

    static readonly string sharedName = LegacyPreferences.GetPrivatePreferencesSharedName("versiontracking");

    static Dictionary<string, List<string>> versionTrail;
    static string LastInstalledVersion => versionTrail[versionsKey].LastOrDefault();
    static string LastInstalledBuild => versionTrail[buildsKey].LastOrDefault();

    public static string VersionsKey => versionsKey;
    public static string BuildsKey => buildsKey;
    public static string SharedName => sharedName;
    public static bool IsFirstLaunchEver { get; private set; }
    public static bool IsFirstLaunchForCurrentVersion { get; private set; }
    public static bool IsFirstLaunchForCurrentBuild { get; private set; }
    public static string CurrentVersion => AppInfo.VersionString;
    public static string CurrentBuild => AppInfo.BuildString;
    public static string PreviousVersion => GetPrevious(versionsKey);
    public static string PreviousBuild => GetPrevious(buildsKey);
    public static string FirstInstalledVersion => versionTrail[versionsKey].FirstOrDefault();
    public static string FirstInstalledBuild => versionTrail[buildsKey].FirstOrDefault();
    public static IEnumerable<string> VersionHistory => versionTrail[versionsKey].ToArray();
    public static IEnumerable<string> BuildHistory => versionTrail[buildsKey].ToArray();
    public static bool IsFirstLaunchForVersion(string version) => CurrentVersion == version && IsFirstLaunchForCurrentVersion;
    public static bool IsFirstLaunchForBuild(string build) => CurrentBuild == build && IsFirstLaunchForCurrentBuild;

    static LegacyVersionTracking()
    {
        InitVersionTracking();
    }

    internal static void InitVersionTracking()
    {
        IsFirstLaunchEver = !LegacyPreferences.ContainsKey(versionsKey, sharedName) || !LegacyPreferences.ContainsKey(buildsKey, sharedName);
        if (IsFirstLaunchEver)
        {
            versionTrail = new Dictionary<string, List<string>>
                {
                    { versionsKey, new List<string>() },
                    { buildsKey, new List<string>() }
                };
        }
        else
        {
            versionTrail = new Dictionary<string, List<string>>
                {
                    { versionsKey, ReadHistory(versionsKey).ToList() },
                    { buildsKey, ReadHistory(buildsKey).ToList() }
                };
        }

        IsFirstLaunchForCurrentVersion = !versionTrail[versionsKey].Contains(CurrentVersion) || CurrentVersion != LastInstalledVersion;
        if (IsFirstLaunchForCurrentVersion)
        {
            // Avoid duplicates and move current version to end of list if already present
            versionTrail[versionsKey].RemoveAll(v => v == CurrentVersion);
            versionTrail[versionsKey].Add(CurrentVersion);
        }

        IsFirstLaunchForCurrentBuild = !versionTrail[buildsKey].Contains(CurrentBuild) || CurrentBuild != LastInstalledBuild;
        if (IsFirstLaunchForCurrentBuild)
        {
            // Avoid duplicates and move current build to end of list if already present
            versionTrail[buildsKey].RemoveAll(b => b == CurrentBuild);
            versionTrail[buildsKey].Add(CurrentBuild);
        }
    }

    static string GetPrevious(string key)
    {
        var trail = versionTrail[key];
        return (trail.Count >= 2) ? trail[trail.Count - 2] : null;
    }

    static string[] ReadHistory(string key) => LegacyPreferences.Get(key, null, sharedName)?.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
}

La classe LegacyVersionTracking utilise la classe LegacyPreferences qui fournit l’accès aux données de suivi des versions stockées par la classe Xamarin.Essentials Preferences à partir de votre application Xamarin.Forms :

Remarque

Pour utiliser ce code, ajoutez-le à une classe nommée LegacyPreferences dans votre projet d’application .NET MAUI.

#if ANDROID || IOS || WINDOWS
namespace MigrationHelpers;

public static partial class LegacyPreferences
{
    internal static string GetPrivatePreferencesSharedName(string feature) => $"{AppInfo.PackageName}.xamarinessentials.{feature}";

    public static bool ContainsKey(string key, string sharedName) => PlatformContainsKey(key, sharedName);
    public static void Remove(string key, string sharedName) => PlatformRemove(key, sharedName);
    public static string Get(string key, string defaultValue, string sharedName) => PlatformGet<string>(key, defaultValue, sharedName);
}
#endif

La classe LegacyPreferences est une classe partial dont l’implémentation restante est spécifique à la plateforme.

Android

Sur Android, la classe LegacyPreferences fournit l’implémentation de conteneur de préférences qui récupère les données des préférences partagées. Le code suivant illustre la classe LegacyPreferences :

Remarque

Pour utiliser ce code, ajoutez-le à une classe nommée LegacyPreferences dans le dossier Platforms\Android de votre projet d’application .NET MAUI.

using System.Globalization;
using Android.Content;
using Android.Preferences;
using Application = Android.App.Application;

namespace MigrationHelpers;

public static partial class LegacyPreferences
{
    static readonly object locker = new object();

    static bool PlatformContainsKey(string key, string sharedName)
    {
        lock (locker)
        {
            using (var sharedPreferences = GetSharedPreferences(sharedName))
            {
                return sharedPreferences.Contains(key);
            }
        }
    }

    static void PlatformRemove(string key, string sharedName)
    {
        lock (locker)
        {
            using (var sharedPreferences = GetSharedPreferences(sharedName))
            using (var editor = sharedPreferences.Edit())
            {
                editor.Remove(key).Apply();
            }
        }
    }

    static T PlatformGet<T>(string key, T defaultValue, string sharedName)
    {
        lock (locker)
        {
            object value = null;
            using (var sharedPreferences = GetSharedPreferences(sharedName))
            {
                if (defaultValue == null)
                {
                    value = sharedPreferences.GetString(key, null);
                }
                else
                {
                    switch (defaultValue)
                    {
                        case int i:
                            value = sharedPreferences.GetInt(key, i);
                            break;
                        case bool b:
                            value = sharedPreferences.GetBoolean(key, b);
                            break;
                        case long l:
                            value = sharedPreferences.GetLong(key, l);
                            break;
                        case double d:
                            var savedDouble = sharedPreferences.GetString(key, null);
                            if (string.IsNullOrWhiteSpace(savedDouble))
                            {
                                value = defaultValue;
                            }
                            else
                            {
                                if (!double.TryParse(savedDouble, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var outDouble))
                                {
                                    var maxString = Convert.ToString(double.MaxValue, CultureInfo.InvariantCulture);
                                    outDouble = savedDouble.Equals(maxString) ? double.MaxValue : double.MinValue;
                                }

                                value = outDouble;
                            }
                            break;
                        case float f:
                            value = sharedPreferences.GetFloat(key, f);
                            break;
                        case string s:
                            // the case when the string is not null
                            value = sharedPreferences.GetString(key, s);
                            break;
                    }
                }
            }

            return (T)value;
        }
    }

    static ISharedPreferences GetSharedPreferences(string sharedName)
    {
        var context = Application.Context;

        return string.IsNullOrWhiteSpace(sharedName) ?
#pragma warning disable CS0618 // Type or member is obsolete
            PreferenceManager.GetDefaultSharedPreferences(context) :
#pragma warning restore CS0618 // Type or member is obsolete
                context.GetSharedPreferences(sharedName, FileCreationMode.Private);
    }
}

iOS

Sur iOS, la classe LegacyPreferences fournit l’implémentation de conteneur de préférences qui récupère les données de NSUserDefaults. Le code suivant illustre la classe LegacyPreferences :

Remarque

Pour utiliser ce code, ajoutez-le à une classe nommée LegacyPreferences dans le dossier Platforms\iOS de votre projet d’application .NET MAUI.

using Foundation;
using System.Globalization;

namespace MigrationHelpers;

public static partial class LegacyPreferences
{
    static readonly object locker = new object();

    static bool PlatformContainsKey(string key, string sharedName)
    {
        lock (locker)
        {
            return GetUserDefaults(sharedName)[key] != null;
        }
    }

    static void PlatformRemove(string key, string sharedName)
    {
        lock (locker)
        {
            using (var userDefaults = GetUserDefaults(sharedName))
            {
                if (userDefaults[key] != null)
                    userDefaults.RemoveObject(key);
            }
        }
    }

    static T PlatformGet<T>(string key, T defaultValue, string sharedName)
    {
        object value = null;

        lock (locker)
        {
            using (var userDefaults = GetUserDefaults(sharedName))
            {
                if (userDefaults[key] == null)
                    return defaultValue;

                switch (defaultValue)
                {
                    case int i:
                        value = (int)(nint)userDefaults.IntForKey(key);
                        break;
                    case bool b:
                        value = userDefaults.BoolForKey(key);
                        break;
                    case long l:
                        var savedLong = userDefaults.StringForKey(key);
                        value = Convert.ToInt64(savedLong, CultureInfo.InvariantCulture);
                        break;
                    case double d:
                        value = userDefaults.DoubleForKey(key);
                        break;
                    case float f:
                        value = userDefaults.FloatForKey(key);
                        break;
                    case string s:
                        // the case when the string is not null
                        value = userDefaults.StringForKey(key);
                        break;
                    default:
                        // the case when the string is null
                        if (typeof(T) == typeof(string))
                            value = userDefaults.StringForKey(key);
                        break;
                }
            }
        }

        return (T)value;
    }

    static NSUserDefaults GetUserDefaults(string sharedName)
    {
        if (!string.IsNullOrWhiteSpace(sharedName))
            return new NSUserDefaults(sharedName, NSUserDefaultsType.SuiteName);
        else
            return NSUserDefaults.StandardUserDefaults;
    }
}

Windows

Sur Windows, la classe LegacyVersionTracking fournit l’implémentation de conteneur de préférences qui récupère les données de ApplicationDataContainer. Le code suivant illustre la classe LegacyPreferences :

Remarque

Pour utiliser ce code, ajoutez-le à une classe nommée LegacyPreferences dans le dossier Platforms\Windows de votre projet d’application .NET MAUI.

using Windows.Storage;

namespace MigrationHelpers;

public static partial class LegacyPreferences
{
    static readonly object locker = new object();

    static bool PlatformContainsKey(string key, string sharedName)
    {
        lock (locker)
        {
            var appDataContainer = GetApplicationDataContainer(sharedName);
            return appDataContainer.Values.ContainsKey(key);
        }
    }

    static void PlatformRemove(string key, string sharedName)
    {
        lock (locker)
        {
            var appDataContainer = GetApplicationDataContainer(sharedName);
            if (appDataContainer.Values.ContainsKey(key))
                appDataContainer.Values.Remove(key);
        }
    }

    static T PlatformGet<T>(string key, T defaultValue, string sharedName)
    {
        lock (locker)
        {
            var appDataContainer = GetApplicationDataContainer(sharedName);
            if (appDataContainer.Values.ContainsKey(key))
            {
                var tempValue = appDataContainer.Values[key];
                if (tempValue != null)
                    return (T)tempValue;
            }
        }

        return defaultValue;
    }

    static ApplicationDataContainer GetApplicationDataContainer(string sharedName)
    {
        var localSettings = ApplicationData.Current.LocalSettings;
        if (string.IsNullOrWhiteSpace(sharedName))
            return localSettings;

        if (!localSettings.Containers.ContainsKey(sharedName))
            localSettings.CreateContainer(sharedName, ApplicationDataCreateDisposition.Always);

        return localSettings.Containers[sharedName];
    }
}

Utiliser les données de suivi des versions héritées

La classe LegacyVersionTracking peut être utilisée pour récupérer les données de suivi des versions sur Android, iOS et Windows qui ont été créées avec une version précédente de Xamarin.Forms de votre application :

#if ANDROID || IOS || WINDOWS
using MigrationHelpers;
...

string isFirstLaunchEver = LegacyVersionTracking.IsFirstLaunchEver.ToString();
string currentVersionIsFirst = LegacyVersionTracking.IsFirstLaunchForCurrentVersion.ToString();
string currentBuildIsFirst = LegacyVersionTracking.IsFirstLaunchForCurrentBuild.ToString();
string currentVersion = LegacyVersionTracking.CurrentVersion.ToString();
string currentBuild = LegacyVersionTracking.CurrentBuild.ToString();
string firstInstalledVer = LegacyVersionTracking.FirstInstalledVersion.ToString();
string firstInstalledBuild = LegacyVersionTracking.FirstInstalledBuild.ToString();
string versionHistory = String.Join(',', LegacyVersionTracking.VersionHistory);
string buildHistory = String.Join(',', LegacyVersionTracking.BuildHistory);
string previousVersion = LegacyVersionTracking.PreviousVersion?.ToString() ?? "none";
string previousBuild = LegacyVersionTracking.PreviousBuild?.ToString() ?? "none";
#endif

Cet exemple montre comment utiliser la classe LegacyVersionTracking pour lire les données de suivi des versions héritées. Toutefois, ces données ne peuvent pas être affectées à la classe VersionTracking de .NET MAUI, car ses propriétés ne peuvent pas être définies. Au lieu de cela, les données peuvent être écrites dans les préférences MAUI .NET avec la méthode WriteHistory :

void WriteHistory(string key, IEnumerable<string> history)
{
    Preferences.Default.Set(key, string.Join("|", history), $"{AppInfo.Current.PackageName}.microsoft.maui.essentials.versiontracking");
}

#if ANDROID || IOS || WINDOWS
WriteHistory(LegacyVersionTracking.VersionsKey, LegacyVersionTracking.VersionHistory);
WriteHistory(LegacyVersionTracking.BuildsKey, LegacyVersionTracking.BuildHistory);
#endif

Une fois que les données de suivi de version héritées ont été écrites dans les préférences MAUI .NET, elles peuvent être consommées par la classe VersionTracking de .NET MAUI :

var mauiVersionHistory = VersionTracking.Default.VersionHistory;
var mauiBuildHistory = VersionTracking.Default.BuildHistory;

Les données de suivi des versions héritées peuvent ensuite être supprimées de l’appareil :

#if ANDROID || IOS || WINDOWS
LegacyPreferences.Remove(LegacyVersionTracking.VersionsKey, LegacyVersionTracking.SharedName);
LegacyPreferences.Remove(LegacyVersionTracking.BuildsKey, LegacyVersionTracking.SharedName);
#endif