Posted at

[Unity][Firebase]SignInWithEmailAndPasswordAsync後の処理が実行されない


実行環境


  • macOS 10.14.3

  • Unity 2018.3.7f1

  • Firebase Unity SDK 6.1.1


エラーの内容

UnityでFirebase Authを使う時、「ログインした後にSceneを変えたい」ということはよくあると思います。

ですが、それを素直に書くと処理が実行されず、エラーログなども出ません。


LoginButton.cs

using System.Collections;

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Firebase;
using Firebase.Auth;

public class LoginProcedure: MonoBehaviour
{
public Text email; // メールアドレスを入力するテキストボックスの値
public Text password; // パスワードを入力するテキストボックスの値
public Text message; // ログインが成功したかユーザーに知らせるためのテキストオブジェクト

Firebase.Auth.FirebaseAuth auth;

private void Awake()
{
message.text = "";
auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
}

// ログインボタンをクリックした時の処理
public void OnClick()
{
message.text = "ログイン中..."; // これは表示される

auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task =>
{
if (task.IsCanceled) {
Debug.Log("SignInWithEmailAndPasswordAsync was canceled.");
return;
}
if (task.IsFaulted) {
Debug.Log("SignInWithEmailAndPasswordAsync encountered an error: " + task.Exception);
い。";
return;
}

Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.Log("User signed in successfully: " + newUser.DisplayName + " " + newUser.UserId); // このログは出る

message.text = "ログインしました!"; // 表示されない
SceneManager.LoadScene("OtherScene"); // シーン遷移しない
});
}
}



解決策

LoadScene()などはmain threadで実行しなければなりません。

解決策は主に2つあります。それぞれサンプルコードで説明していきます。


  1. Update()内でflagを監視して、flagが変化したら処理を実行する

  2. 関数を投げるとmain threadで実行してくれるクラスを作成する

2の方が他のスクリプトで使いまわせるので便利だと思います。


Update()内でflagを監視して、flagが変化したら処理を実行する

CreateUserWithEmailAndPasswordAsync()SendEmailVerificationAsync()でも同じ現象が発生するので、ここではCreateUserWithEmailAndPasswordAsync()を例にします。

流れとしては、

* 登録ボタンを押すとOnClick()が実行される

* CreateUserWithEmailAndPasswordAsync ()でユーザーが作成されるとisCreatedフラグがtrueになる

* Update()でisCreatedを監視、trueならloadScene()などの処理を実行する

となります。


RegisterButton.cs

using System;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Firebase;
using Firebase.Auth;

public class RegisterButton : MonoBehaviour
{
public Text email, password, message;

Firebase.Auth.FirebaseAuth auth;
Firebase.Auth.FirebaseUser user;

private bool isCreated, isProcessedAfterCreating;

private void Awake()
{
message.text = "";
auth = Firebase.Auth.FirebaseAuth.DefaultInstance;

isCreated = false; // ユーザーが作成されたらtrue
isProcessedAfterCreating = false; // ユーザー作成後の処理が実行されたらtrue
}

public void OnClick()
{
Debug.Log("email:" + email.text);
Debug.Log("password:" + password.text);

message.text = "アカウント作成中...";

auth.CreateUserWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task =>
{
if (task.IsCanceled)
{
Debug.Log("CreateUserWithEmailAndPasswordAsync was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.Log("CreateUserWithEmailAndPasswordAsync encountered an error: " + task.Exception);
return;
}

user = task.Result;
Debug.Log("Firebase user created successfully: " + user.DisplayName + " " + user.UserId);

isCreated = true; // ユーザーが作成されたフラグ
});
}

void Update()
{
// ユーザーが作成されて、ユーザー作成後の処理がされていない場合
if (isCreated && !isProcessedAfterCreating) {
processAfterCreating();
}
}

void processAfterCreating()
{
isProcessedAfterCreating = true; // ユーザー作成後の処理を実行したフラグ
message.text = "アカウントが作成されました!";
SceneManager.LoadScene("OtherScene");
}
}



関数を投げるとmain threadで実行してくれるクラスを作成する

まずThreadDispatcherというクラスを作成します。(参考


ThreadDispatcher.cs

internal class ThreadDispatcher {

private int ownerThreadId;
private System.Collections.Generic.Queue<System.Action> queue =
new System.Collections.Generic.Queue<System.Action>();

public ThreadDispatcher() {
ownerThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
}

private class CallbackStorage<TResult> {
public TResult Result { get; set; }
public System.Exception Exception { get; set; }
}

// Triggers the job to run on the main thread, and waits for it to finish.
public TResult Run<TResult>(System.Func<TResult> callback) {
if (ManagesThisThread()) {
return callback();
}

var waitHandle = new System.Threading.EventWaitHandle(
false, System.Threading.EventResetMode.ManualReset);

var result = new CallbackStorage<TResult>();
lock(queue) {
queue.Enqueue(() => {
try {
result.Result = callback();
}
catch (System.Exception e) {
result.Exception = e;
}
finally {
waitHandle.Set();
}
});
}
waitHandle.WaitOne();
if (result.Exception != null) throw result.Exception;
return result.Result;
}

// Determines whether this thread is managed by this instance.
internal bool ManagesThisThread() {
return System.Threading.Thread.CurrentThread.ManagedThreadId == ownerThreadId;
}

// This dispatches jobs queued up for the owning thread.
// It must be called regularly or the threads waiting for job will be
// blocked.
public void PollJobs() {
System.Diagnostics.Debug.Assert(ManagesThisThread());

System.Action job;
while (true) {
lock(queue) {
if (queue.Count > 0) {
job = queue.Dequeue();
} else {
break;
}
}
job();
}
}
}


loadScene()などはこのクラスを使ってmain threadで実行してもらいます。


LoginButton.cs

using System;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Firebase;
using Firebase.Auth;

public class LoginProcedure: MonoBehaviour
{
public Text email, password, message;

Firebase.Auth.FirebaseAuth auth;

private ThreadDispatcher dispatcher; // main threadで実行してくれるクラス

private void Awake()
{
dispatcher = new ThreadDispatcher();

message.text = "";
auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
}

public void OnClick()
{
message.text = "ログイン中...";
auth.SignInWithEmailAndPasswordAsync(email.text, password.text).ContinueWith(task =>
{
if (task.IsCanceled) {
Debug.Log("SignInWithEmailAndPasswordAsync was canceled.");
return;
}
if (task.IsFaulted) {
Debug.Log("SignInWithEmailAndPasswordAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.FirebaseUser newUser = task.Result;
Debug.Log("User signed in successfully: " + newUser.DisplayName + " " + newUser.UserId);

// main threadで実行
RunOnMainThread(() =>
{
message.text = "ログインしました!";
SceneManager.LoadScene("ProfileScene");
return 0;
});
});
}

void Update()
{
dispatcher.PollJobs(); // 未処理のジョブがあれば実行
}

public TResult RunOnMainThread<TResult>(System.Func<TResult> f)
{
return dispatcher.Run(f);
}
}



まとめ

UnityでFirebase Authを使った時に、遭遇しそうなエラーについて書きました。

何かあれば編集リクエストなどお願いいたします。