現在、アプリ全体のライフサイクル中にアクセスするために使用する予定のシングルトンモデルとともに、Firebaseをテストしています。ささいなことで立ち往生していますが、私の人生ではそれを理解できません。私が使用しているモデルのサンプルがあります。Firebaseのブックマークです。
public class BookSingleton {
private static BookSingleton model;
private ArrayList<BookMark> bookmarks = new ArrayList<BookMark>();
public static BookSingleton getModel()
{
if (model == null)
{
throw new IllegalStateException("The model has not been initialised yet.");
}
return model;
}
public ArrayList<Bookmark> theBookmarkList()
{
return this.bookmarks;
}
public void setBookmarks(ArrayList<Bookmark> bookmarks){
this.bookmarks = bookmarks;
}
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
}
//bookmarks still exist here at this point
setBookmarks(loadedBookmarks);
}
@Override
public void onCancelled(FirebaseError firebaseError) {
}
});
//by now loadedBookmarks is empty
//this is probably the issue?
//even without this line bookmarks is still not set in mainactivity
setBookmarks(loadedBookmarks);
}
シングルトンセットのインスタンスでmainActivityを開始すると、nullエラーが発生します。これは、firebaseからモデルデータをロードするために記述した関数が何も設定しないためです。
このようなもの:MainActivity
public class MainActivity extends AppCompatActivity {
private BookSingleton theModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the model
theModel = BookSingleton.getModel(this);
//manually setting this works
// ArrayList<Book> bookSamples = new ArrayList<Book>;
// bookSamples.add(aBookSample);
theModel.loadModelWithSampleData(bookSamples);
//should have set the singleton model property Bookmarks to the results from firebase
theModel.loadModelWithDataFromFirebase();
//returns 0
Log.d(TAG, "" + theModel.theBookmarkList().size());
setContentView(R.layout.activity_main);
//......rest of code
どうすればこれを機能させることができますか?
Firebaseはデータを読み込み、同期します非同期。したがって、あなたのloadModelWithDataFromFirebase()
は、読み込みが完了するのを待たずに、データベースからデータの読み込みを開始します。 loadModelWithDataFromFirebase()
関数が戻るまでに、ロードはまだ完了していません。
いくつかの適切に配置されたログステートメントを使用して、これを自分で簡単にテストできます。
public void loadModelWithDataFromFirebase(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Log.v("Async101", "Start loading bookmarks");
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.v("Async101", "Done loading bookmarks");
//getting all properties from firebase...
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
}
@Override
public void onCancelled(FirebaseError firebaseError) { }
});
Log.v("Async101", "Returning loaded bookmarks");
setBookmarks(loadedBookmarks);
}
予想に反して、ログステートメントの順序は次のようになります。
Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
この読み込みの非同期の性質を処理するには、2つの選択肢があります。
非同期のバグをつぶします(通常、「間違いでした。これらの人々は自分が何をしているのか知らない」のようなフレーズをつぶやきます)。
非同期の獣を受け入れます(通常、かなりの時間の呪いを伴いますが、しばらくすると平和で動作の良いアプリケーションが登場します)
最初のオプションを選択したい場合は、適切に配置された同期プリミティブでうまくいきます。
public void loadModelWithDataFromFirebase() throws InterruptedException {
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
Semaphore semaphore = new Semaphore(0);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
semaphore.release();
}
@Override
public void onCancelled(FirebaseError firebaseError) { throw firebaseError.toException(); }
});
semaphore.acquire();
setBookmarks(loadedBookmarks);
}
更新(20160303):これをAndroidでテストしたところ、アプリがブロックされました。これは通常のJVMで問題なく動作しますが、Androidはスレッドに関しては細心の注意が必要です。お気軽に試してみてください...または
代わりに非同期プログラミングを採用する場合は、アプリケーションのロジックを再考する必要があります。
現在、「最初にブックマークをロードします。次にサンプルデータをロードします。次にさらにロードします。」
非同期読み込みモデルでは、「ブックマークが読み込まれたらいつでもサンプルデータを読み込みたい。サンプルデータが読み込まれたらもっと読みたい」と考える必要があります。
このように考えることのボーナスは、データが絶えず変化し、したがって複数回同期される可能性がある場合にも機能することです:「ブックマークが変更されるたびに、サンプルデータもロードしたいのです。もっと。"
コードでは、これはネストされた呼び出しまたはイベントチェーンにつながります。
public void synchronizeBookmarks(){
Firebase db = new Firebase(//url);
Firebase bookmarksRef = fb.child(//access correct child);
final ArrayList<Bookmark> loadedBookmarks = new ArrayList<Bookmark>();
bookmarksRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Bookmark bookmark = new Bookmark(//properties here);
loadedBookmarks.add(bookmark);
setBookmarks(loadedBookmarks);
loadSampleData();
}
@Override
public void onCancelled(FirebaseError firebaseError) { throw firebaseError.toException(); }
});
}
上記のコードでは、単一の値のイベントを待つだけでなく、すべてのイベントを処理します。これは、ブックマークが変更されるたびにonDataChange
が実行され、サンプルデータ(またはアプリケーションのニーズに適合するその他のアクション)を(再)ロードすることを意味します。
コードをより再利用可能にするために、onDataChange
で正確なコードを呼び出すのではなく、独自のコールバックインターフェイスを定義することができます。その良い例は この答え をご覧ください。
別の post で述べたように、Promiseを使用してFirebaseの非同期の性質に対処できます。それは次のようになります:
public Task<List<Data>> synchronizeBookmarks(List<Bookmark> bookmarks) {
return Tasks.<Void>forResult(null)
.then(new GetBook())
.then(new AppendBookmark(bookmarks))
.then(new LoadData())
}
public void synchronizeBookmarkWithListener() {
synchronizeBookmarks()
.addOnSuccessListener(this)
.addOnFailureListener(this);
}
Android向けGoogle API は タスクフレームワーク を提供します( Parse が Bolts と同じように)、これは-に似ています JavaScript promises コンセプト。
まず、Firebaseからブックマークをダウンロードするための Task
を作成します。
class GetBook implements Continuation<Void, Task<Bookmark>> {
@Override
public Task<Bookmark> then(Task<Void> task) {
TaskCompletionSource<Bookmark> tcs = new TaskCompletionSource();
Firebase db = new Firebase("url");
Firebase bookmarksRef = db.child("//access correct child");
bookmarksRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
tcs.setResult(dataSnapshot.getValue(Bookmark.class));
}
});
tcs.getTask();
}
}
アイデアを得たので、setBookmarks
とloadSampleData
も非同期であるとします。シーケンスで実行される Continuation
タスク(前のタスクと同様)として作成することもできます。
class AppendBookmark(List<Bookmark> bookmarks) implements
Continuation<List<Bookmark>, Task<Bookmark> {
final List<Bookmark> bookmarks;
LoadBookmarks(List<Bookmark> bookmarks) {
this.bookmark = bookmark;
}
@Override
Task<List<Bookmark>> then(Task<Bookmark> task) {
TaskCompletionSource<List<Bookmark>> tcs = new TaskCompletionSource();
bookmarks.add(task.getResult());
tcs.setResult(this.bookmarks);
return tcs.getTask();
}
}
class LoadSampleData implements Continuation<List<Bookmark>, List<Data>> {
@Override
public Task<List<Data>> then(Task<List<Bookmark>> task) {
// ...
}
}
クラスがロードされたら、シングルトンを初期化する必要があります。これをあなたのコードに入れてください:
private static BookSingleton model = new BookSingleton();
private BookSingleton() {
}
public static BookSingleton getModel() { return model == null ? new BookSingleton() : model;
}