Как использовать async / await с SDK, который ожидает асинхронную активность на основе обратного вызова - PullRequest
0 голосов
/ 10 июня 2018

Я внедряю аутентификацию Android на Facebook в Xamarin Forms и борюсь с SDK Facebook, ломая цепочку ожидания / асинхронности.SDK требует передачи реализаций IFacebookCallback и GraphRequest.IGraphJSONObjectCallback, которые определяют обратные вызовы для обработки результатов.Я хотел бы сделать следующее в коде, который запрашивает логин:

public async Task LogInFacebookAsync()
{
    var loginResult = await _facebookManager.Login();  
    // do stuff with result
}

Однако вот что я сейчас делаю.
Код, который запрашивает логин:

public void LogInFacebookAsync()
{
    _facebookManager.Login(onFacebookLoginComplete);
}
private async Task<string> onFacebookLoginComplete(FacebookUser fbUser, string errorMessage) 
{
    // do stuff with result
}

Вот мой класс FacebookManager, который реализует необходимые интерфейсы.Обратите внимание, что результаты могут быть получены из нескольких методов обратного вызова: OnCancel(), OnError(), OnCompleted(), каждый из которых вызывает _onLoginComplete, как передано в коде выше.

public class FacebookManager : Java.Lang.Object, IFacebookManager, IFacebookCallback, GraphRequest.IGraphJSONObjectCallback
{
    public Func<FacebookUser, string, Task> _onLoginComplete;
    public ICallbackManager _callbackManager;
    private Activity _context;

    public FacebookManager()
    {
        this._context = CrossCurrentActivity.Current.Activity;
        _callbackManager = CallbackManagerFactory.Create();
        LoginManager.Instance.RegisterCallback(_callbackManager, this);
    }

    public async Task Login(Func<FacebookUser, string, Task> onLoginComplete)
    {
        _onLoginComplete = onLoginComplete;
        LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
        LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
        await Task.CompletedTask;
    }

    #region IFacebookCallback   
    public void OnCancel()
    {
        _onLoginComplete?.Invoke(null, "Canceled!");
    }
    public void OnError(FacebookException error)
    {
        _onLoginComplete?.Invoke(null, error.Message);
    }       
    public void OnSuccess(Java.Lang.Object result)
    {
        var n = result as LoginResult;
        if (n != null)
        {
            var request = GraphRequest.NewMeRequest(n.AccessToken, this);
            var bundle = new Android.OS.Bundle();
            bundle.PutString("fields", "id, first_name, email, last_name, picture.width(500).height(500)");
            request.Parameters = bundle;
            request.ExecuteAsync();
        }
    }
    #endregion

    #region GraphRequest.IGraphJSONObjectCallback
    public void OnCompleted(JSONObject p0, GraphResponse p1)
    {       
        // extract user
        _onLoginComplete?.Invoke(fbUser, string.Empty);   
    }
    #endregion
}

Можно ли как-нибудь обернуть эти операции, чтобы я был "полностью асинхронным"?

Обновление:

Спасибо, Пауло, за отличный ответ .Я уже думал в том же духе, а вы действительно выкристаллизовали решение.Мое единственное беспокойство с ответом было о том, что это потокобезопасныйЧто, если дважды щелкнуть кнопку или сделать два вызова API одновременно, которые требуют входа в Facebook?Далее я попытаюсь сделать метод LoginAsync() более безопасным.Однако я не специалист по безопасности потоков и приветствую любую критику.

private TaskCompletionSource<FacebookUser> _loginTcs;
public async Task<FacebookUser> LoginAsync()
{
    Task<FacebookUser> loginTask = _loginTcs?.Task;
    // Don't want to create a new login request to the Facebook API if one is already being made.
    if (loginTask != null && (loginTask.Status == TaskStatus.Running || loginTask.Status == TaskStatus.WaitingForActivation 
        || loginTask.Status == TaskStatus.WaitingToRun || loginTask.Status == TaskStatus.WaitingForChildrenToComplete))
    {
        return await loginTask.ConfigureAwait(false);
    }

    this._loginTcs = new TaskCompletionSource<FacebookUser>();
    LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
    LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
    var user = await this._loginTcs.Task.ConfigureAwait(false);
    return user;
}

1 Ответ

0 голосов
/ 10 июня 2018

Использовать TaskCompletionSource .Больше ничего не нужно.

Я не знаю много об этом API (и я удивлен, что Xamarin не предоставляет идиоматическую версию .NET этого), но, примерно, вам нужно сделать следующее::

Добавьте источник завершения задачи в ваш класс вместо _onLoginComplete делегата:

// public Func<FacebookUser, string, Task> _onLoginComplete;
private TaskCompletionSource<FacebookUser> completion;

Измените метод OnCancel, чтобы отменить задачу:

public void OnCancel()
{
    this.completion.TrySetCanceled();
}

Измените метод OnError, чтобы завершить задачу с ошибкой:

public void OnError(FacebookException error)
{
    this.completion.TrySetException(error);
}

Измените OnCompleted, чтобы задать результат задачи:

public void OnCompleted(JSONObject p0, GraphResponse p1)
{       
    // extract user
    this.completion.TrySetResult(fbUser);   
}

Измените метод входа, чтобы создатьисточник завершения задачи и возврат задачи источника завершения задачи:

public async Task<FacebookUser> LoginAsync()
{
    this.completion = new TaskCompletionSource<FacebookUser>();
    LoginManager.Instance.SetLoginBehavior(LoginBehavior.NativeWithFallback);
    LoginManager.Instance.LogInWithReadPermissions(_context, new List<string> { "public_profile", "email" });
    await this.completion.Task;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...