Androidアプリで、Firebase経由で接続されたピアから取得したデータでWebViewの一部を更新しようとしています。そのためには、ブロック操作を実行すると、次のメッセージが返されます。必要なデータ。たとえば、別のチャット参加者が何かを書き込んでからPush.setValue()が返されるまで待機する、チャットの例の実装。Firebaseでこのような動作は可能ですか?
通常のJVMでは、通常のJava同期プリミティブを使用してこれを行います。
例えば:
// create a Java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// tell the caller that we're done
semaphore.release();
}
@Override
public void onCancelled(FirebaseError firebaseError) {
}
});
// wait until the onDataChange callback has released the semaphore
semaphore.acquire();
// send our response message
ref.Push().setValue("Oh really? Here is what I think of that");
しかし、これはAndroidでは機能しません。そして、それは良いことです。なぜなら、ユーザーインターフェイスに影響を与えるものすべてにこのタイプのブロッキングアプローチを使用することは悪い考えだからです。このコードが横になっている唯一の理由は、単体テストが必要だったからです。
実際のユーザー向けコードでは、イベント駆動型アプローチを採用する必要があります。したがって、「データが到着するのを待ってからメッセージを送信する」のではなく、「データが到着したら、メッセージを送信する」ようにします。
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// send our response message
ref.Push().setValue("Oh really? Here is what I think of that!");
}
@Override
public void onCancelled(FirebaseError firebaseError) {
}
});
最終的な結果はまったく同じですが、このコードは同期を必要とせず、Androidではブロックされません。
import com.google.Android.gms.tasks.Tasks;
Tasks.await(taskFromFirebase);
データを同期的にフェッチする別の方法を思いつきました。前提条件は、UIスレッドにないことです。
final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();
firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
tcs.setResult(mapper.map(dataSnapshot));
}
@Override
public void onCancelled(DatabaseError databaseError) {
tcs.setException(databaseError.toException());
}
});
Task<List<Object>> t = tcs.getTask();
try {
Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
t = Tasks.forException(e);
}
if(t.isSuccessful()) {
List<Object> result = t.getResult();
}
ソリューションをテストしましたが、正常に機能していますが、間違っていることを証明してください。
アレックスのコンパクトな答えに基づいたより長い例を次に示します。
import com.google.Android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance();
final CollectionReference chatMessageReference = firestore.collection("chatmessages");
final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments();
final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);
これはnot UIスレッドをブロックするので良い習慣であることに注意してください。一般的には、代わりにコールバックを使用する必要があります。しかし、この特定のケースでは、これが役立ちます。
Kotlinのコルーチンの使用方法についても考えている人がいる場合は、 kotlinx-coroutines-play-services
。
アプリに追加build.gradle
ファイル:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"
次に、単純に:
suspend fun signIn(email: String, password: String) {
try {
val auth: FirebaseAuth = FirebaseAuth.getInstance()
auth.signInWithEmailAndPassword(email, password).await()
} catch (e: FirebaseAuthException) {
println("${e.errorCode}: ${e.message}")
}
}
Androidでタスクを同期的に呼び出すための簡単なクラスを作成しました。
これはJavascript
の非同期待機関数に似ていることに注意してください。
私の 要点 を確認してください。
これを使用するためのサンプルコードです。
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});