Freigeben über


Migrieren von Versionsverfolgungsdaten aus einer Xamarin.Forms-App zu einer .NET MAUI-App

Xamarin.Essentials und .NET Multi-Platform App UI (.NET MAUI) verfügen beide über eine VersionTracking-Klasse, mit der Sie die Versions- und Buildnummern der App überprüfen können, zusammen mit zusätzlichen Informationen, z. B. wann die App zum ersten Mal gestartet wurde. In Xamarin.Essentials werden die Versionsnachverfolgungsdaten jedoch in einem plattformspezifischen Einstellungscontainer mit einem Namen von {your-app-package-id}.xamarinessentials.versiontracking gespeichert, während in .NET MAUI die Versionsnachverfolgungsdaten in einem plattformspezifischen Einstellungscontainer mit einem Namen von {your-app-package-id}.microsoft.maui.essentials.versiontracking gespeichert werden.

Bei der Migration einer Xamarin.Forms-App, die die VersionTracking-Klasse für .NET MAUI verwendet, müssen Sie sich mit diesem Einstellungsunterschied bei der Containerbenennung befassen, um Benutzern eine reibungslose Upgradeerfahrung zu bieten. In diesem Artikel wird beschrieben, wie Sie die LegacyVersionTracking-Klasse und entsprechenden Hilfsklassen verwenden können, um den Einstellungscontainer zu verwalten. Die LegacyVersionTracking-Klasse ermöglicht Ihrer .NET MAUI-App unter Android, iOS und Windows das Lesen von Versionsnachverfolgungsdaten, die mit einer früheren Xamarin.Forms-Version Ihrer App erstellt wurden.

Wichtig

Damit die LegacyVersionTracking-Klasse ordnungsgemäß funktioniert, muss Ihre .NET MAUI-App eine höhere Versionsnummer als die Versionsnummer Ihrer Xamarin.Forms-App aufweisen. Die Versionsnummer kann in der Projektdatei Ihrer .NET MAUI-App mit den Buildeigenschaften $(ApplicationVersion) und $(ApplicationDisplayVersion) festgelegt werden.

Weitere Informationen zur Klasse VersionTracking in Xamarin.Essentials finden Sie unter Xamarin.Essentials: Versionsverfolgung. Weitere Informationen zur Klasse VersionTracking in .NET MAUI finden Sie unter Versionsverfolgung.

Zugreifen auf Nachverfolgungsdaten von Vorversionen

Der folgende Code zeigt die Klasse LegacyVersionTracking, die Zugriff auf die Versionsverfolgungsdaten bietet, die von Ihrer Xamarin.Forms-App erstellt wurden:

Hinweis

Um diesen Code zu verwenden, fügen Sie ihn einer Klasse namens LegacyVersionTracking in Ihrem .NET MAUI-App-Projekt hinzu.

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];
}

Die Klasse LegacyVersionTracking verwendet die Klasse LegacyPreferences, die Zugriff auf Versionsverfolgungsdaten bietet, die von der Xamarin.Essentials-Klasse Preferences aus Ihrer Xamarin.Forms-App gespeichert wurden:

Hinweis

Um diesen Code zu verwenden, fügen Sie ihn einer Klasse namens LegacyPreferences in Ihrem .NET MAUI-App-Projekt hinzu.

#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

Die Klasse LegacyPreferences ist eine partial-Klasse, deren verbleibende Implementierung plattformspezifisch ist.

Android

Unter Android stellt die Klasse LegacyPreferences die Einstellungscontainerimplementierung bereit, die Daten aus freigegebenen Einstellungen abruft. Der folgende Code zeigt die LegacyPreferences-Klasse:

Hinweis

Um diesen Code zu verwenden, fügen Sie ihn zu einer Klasse mit dem Namen LegacyPreferences im Ordner Platforms\Android Ihres .NET MAUI-App-Projekts hinzu.

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

Unter iOS stellt die Klasse LegacyPreferences die Einstellungscontainerimplementierung bereit, mit der Daten von NSUserDefaults abgerufen werden. Der folgende Code zeigt die LegacyPreferences-Klasse:

Hinweis

Um diesen Code zu verwenden, fügen Sie ihn einer Klasse mit dem Namen LegacyPreferences im Ordner Platforms\iOS Ihres .NET MAUI-App-Projekts hinzu.

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

Unter Windows stellt die Klasse LegacyVersionTracking die Einstellungscontainerimplementierung bereit, mit der Daten von ApplicationDataContainer abgerufen werden. Der folgende Code zeigt die LegacyPreferences-Klasse:

Hinweis

Um diesen Code zu verwenden, fügen Sie ihn einer Klasse mit dem Namen LegacyPreferences im Ordner Plattforms\Windows Ihres .NET MAUI-App-Projekts hinzu.

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];
    }
}

Nutzen von Legacy-Versionsnachverfolgungsdaten

Die LegacyVersionTracking-Klasse kann verwendet werden, um Versionsnachverfolgungsdaten unter Android, iOS und Windows abzurufen, die mit einer früheren Xamarin.Forms-Version Ihrer App erstellt wurden:

#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

Dieses Beispiel zeigt die Verwendung der LegacyVersionTracking-Klasse zum Lesen von Legacy-Versionsnachverfolgungsdaten. Diese Daten können jedoch nicht der .NET MAUI-Klasse VersionTracking zugewiesen werden, da ihre Eigenschaften nicht festgelegt werden können. Stattdessen können die Daten mit der Methode WriteHistory in .NET MAUI-Einstellungen geschrieben werden:

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

Nachdem die Nachverfolgungsdaten von Vorversionen in .NET MAUI-Einstellungen geschrieben wurden, können sie von der .NET MAUI-Klasse VersionTracking genutzt werden:

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

Die Nachverfolgungsdaten von Vorversionen können dann vom Gerät entfernt werden:

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