Xamarin.Forms 앱에서 .NET MAUI 앱으로 버전 추적 데이터 마이그레이션

Xamarin.Essentials 및 .NET 다중 플랫폼 앱 UI(.NET MAUI)에는 VersionTracking 모두 앱의 버전 및 빌드 번호를 검사 수 있는 클래스와 앱이 처음 시작된 경우와 같은 추가 정보가 있습니다. 그러나 Xamarin.Essentials에서 버전 추적 데이터는 이름이 {your-app-package-id}.xamarinessentials.versiontracking있는 플랫폼별 기본 설정 컨테이너에 저장되고.NET MAUI에서는 버전 추적 데이터가 이름 {your-app-package-id}.microsoft.maui.essentials.versiontracking인 플랫폼별 기본 설정 컨테이너에 저장됩니다.

클래스를 사용하는 VersionTracking Xamarin.Forms 앱을 .NET MAUI로 마이그레이션하는 경우 사용자에게 원활한 업그레이드 환경을 제공하기 위해 이 기본 설정 컨테이너 명명 차이를 처리해야 합니다. 이 문서에서는 클래스 및 도우미 클래스를 LegacyVersionTracking 사용하여 기본 설정 컨테이너를 처리하는 방법을 설명합니다. 이 LegacyVersionTracking 클래스를 사용하면 Android, iOS 및 Windows의 .NET MAUI 앱에서 이전 Xamarin.Forms 버전의 앱으로 만든 버전 추적 데이터를 읽을 수 있습니다.

Important

클래스가 LegacyVersionTracking 올바르게 작동하려면 .NET MAUI 앱의 버전 번호가 Xamarin.Forms 앱의 버전 번호보다 높아야 합니다. 버전 번호는 .NET MAUI 앱의 프로젝트 파일에서 빌드 속성과 $(ApplicationDisplayVersion) 함께 $(ApplicationVersion) 설정할 수 있습니다.

Xamarin.Essentials의 VersionTracking 클래스에 대한 자세한 내용은 Xamarin.Essentials: 버전 추적을 참조하세요. .NET MAUI의 클래스에 VersionTracking 대한 자세한 내용은 버전 추적을 참조하세요.

레거시 버전 추적 데이터에 액세스

다음 코드는 Xamarin.Forms 앱에서 만든 버전 추적 데이터에 대한 액세스를 제공하는 클래스를 보여 LegacyVersionTracking 줍니다.

참고 항목

이 코드를 사용하려면 .NET MAUI 앱 프로젝트에서 명명된 LegacyVersionTracking 클래스에 추가합니다.

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

클래스는 LegacyVersionTracking Xamarin.Forms 앱에서 Xamarin.Essentials Preferences 클래스에 저장된 버전 추적 데이터에 대한 액세스를 제공하는 클래스를 사용합니다LegacyPreferences.

참고 항목

이 코드를 사용하려면 .NET MAUI 앱 프로젝트에서 명명된 LegacyPreferences 클래스에 추가합니다.

#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

LegacyPreferences 클래스는 partial 다시 기본 구현이 플랫폼별 클래스입니다.

Android

Android에서 클래스는 LegacyPreferences 공유 기본 설정에서 데이터를 검색하는 기본 설정 컨테이너 구현을 제공합니다. 다음 코드에서는 LegacyPreferences 클래스를 보여 줍니다.

참고 항목

이 코드를 사용하려면 .NET MAUI 앱 프로젝트의 Platforms\Android 폴더에 있는 LegacyPreferences 클래스에 추가합니다.

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

iOS에서 클래스는 LegacyPreferences 데이터를 NSUserDefaults검색하는 기본 설정 컨테이너 구현을 제공합니다. 다음 코드에서는 LegacyPreferences 클래스를 보여 줍니다.

참고 항목

이 코드를 사용하려면 .NET MAUI 앱 프로젝트의 Platforms\iOS 폴더에 명명된 LegacyPreferences 클래스에 추가합니다.

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

Windows에서 클래스는 LegacyVersionTracking 데이터를 ApplicationDataContainer검색하는 기본 설정 컨테이너 구현을 제공합니다. 다음 코드에서는 LegacyPreferences 클래스를 보여 줍니다.

참고 항목

이 코드를 사용하려면 .NET MAUI 앱 프로젝트의 Platforms\Windows 폴더에 있는 LegacyPreferences 클래스에 추가합니다.

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

레거시 버전 추적 데이터 사용

이 클래스를 LegacyVersionTracking 사용하여 이전 Xamarin.Forms 버전의 앱으로 만든 Android, iOS 및 Windows에서 버전 추적 데이터를 검색할 수 있습니다.

#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

이 예제에서는 클래스를 LegacyVersionTracking 사용하여 레거시 버전 추적 데이터를 읽는 방법을 보여 줍니다. 그러나 속성을 설정할 수 없으므로 .NET MAUI의 VersionTracking 클래스에 이 데이터를 할당할 수 없습니다. 대신 메서드를 사용하여 .NET MAUI 기본 설정 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

레거시 버전 추적 데이터가 .NET MAUI 기본 설정에 기록되면 .NET MAUI의 VersionTracking 클래스에서 사용할 수 있습니다.

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

그런 다음, 레거시 버전 추적 데이터를 디바이스에서 제거할 수 있습니다.

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