Xamarin.Authからの移行

## 背景

Visual Studio for Mac を 2019 にアップデートしたら Xamarin.iOS のアプリが動かなくなった。

Xamarin.Auth の `AccountStore.FindAccountsForServiceAsync` を呼ぶだけでエラーが起きるようになっている。

<https://github.com/xamarin/Xamarin.Auth/issues/370> にissueが上がっており、対策は

1. Xamarin.iOSのダウングレード(12.2)
2. Xamarin.Essentials の `SecureStorage` への移行。

のいずれか。

前者は影響範囲が全アプリに及ぶうえに、Xamarin.Auth のこのクラスは非推奨のため、後者を選択。

## やり方

[Migration Guide](https://github.com/xamarin/Xamarin.Auth/wiki/Migrating-from-AccountStore-to-Xamarin.Essentials-SecureStorage) にあるように、次のようなヘルパークラスをつくって移行すればよい。

```csharp
using Newtonsoft.Json;
using Xamarin.Auth;
using Xamarin.Essentials;

public class SecureStorageAccountStore
{
    public static async Task SaveAsync(Account account, string serviceId)
    {
        // Find existing accounts for the service
        var accounts = await FindAccountsForServiceAsync(serviceId);

        // Remove existing account with Id if exists
        accounts.RemoveAny(a => a.Username == account.Username);

        // Add account we are saving
        accounts.Add(account);

        // Serialize all the accounts to javascript
        var json = JsonConvert.SerializeObject(accounts);

        // Securely save the accounts for the given service
        await SecureStorage.SetAsync(serviceId, json);
    }

    public static async Task<List<Account>> FindAccountsForServiceAsync(string serviceId)
    {
        // Get the json for accounts for the service
        var json = await SecureStorage.GetAsync(serviceId);

        try {
            // Try to return deserialized list of accounts
            return JsonConvert.DeserializeObject<List<Account>>(json);
        } catch { }

        // If this fails, return an empty list
        return new List<Account>();
    }

    public static async Task MigrateAllAccountsAsync(string serviceId, IEnumerable<Account> accountStoreAccounts)
    {
        var wasMigrated = await SecureStorage.GetAsync("XamarinAuthAccountStoreMigrated", "0");

        if (wasMigrated == "1")
            return;

        await SecureStorage.SetAsync("XamarinAuthAccountStoreMigrated", "1");

        // Just in case, look at existing 'new' accounts
        var accounts = await FindAccountsForServiceAsync(serviceId);

        foreach (var account in accountStoreAccounts) {

            // Check if the new storage already has this account
            // We don't want to overwrite it if it does
            if (accounts.Any(a => a.Username == account.Username))
                continue;   
            
            // Add the account we are migrating
            accounts.Add(account);
        }

        // Serialize all the accounts to javascript
        var json = JsonConvert.SerializeObject(accounts);

        // Securely save the accounts for the given service
        await SecureStorage.SetAsync(serviceId, json);
    }
}
```

※ 上は挙げられているコードそのままだが、実際には System.Linq などの import をたす必要あり。

### 問題

が、結局この前後のデータのマイグレーションにも `Find...` が必要なため、この事態が起きてしまうということ。許容できない場合は Xamarin.Native でキーチェーンにアクセスして取り出す処理を書く必要がある。

もう一点、iOS SDK の問題だが、シミュレータ上では Entitlements.plist の編集が必要になる。通常（実機で使う場合）は不要のため、地味にストレスになる。

<https://docs.microsoft.com/ja-jp/xamarin/essentials/secure-storage?context=xamarin%2Fandroid&tabs=ios>