web-dev-qa-db-ja.com

Firebaseリスナーでのシングルトンプロパティ値の設定

現在、アプリ全体のライフサイクル中にアクセスするために使用する予定のシングルトンモデルとともに、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

どうすればこれを機能させることができますか?

22
Anthony Wijaya

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つの選択肢があります。

  1. 非同期のバグをつぶします(通常、「間違いでした。これらの人々は自分が何をしているのか知らない」のようなフレーズをつぶやきます)。

  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はスレッドに関しては細心の注意が必要です。お気軽に試してみてください...または

赤い丸薬を取る-Firebaseでのデータ同期の非同期の性質に対処する

代わりに非同期プログラミングを採用する場合は、アプリケーションのロジックを再考する必要があります。

現在、「最初にブックマークをロードします。次にサンプルデータをロードします。次にさらにロードします。」

非同期読み込みモデルでは、「ブックマークが読み込まれたらいつでもサンプルデータを読み込みたい。サンプルデータが読み込まれたらもっと読みたい」と考える必要があります。

このように考えることのボーナスは、データが絶えず変化し、したがって複数回同期される可能性がある場合にも機能することです:「ブックマークが変更されるたびに、サンプルデータもロードしたいのです。もっと。"

コードでは、これはネストされた呼び出しまたはイベントチェーンにつながります。

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で正確なコードを呼び出すのではなく、独自のコールバックインターフェイスを定義することができます。その良い例は この答え をご覧ください。

28

TL; DR:Firebaseの非同期性を受け入れる

別の 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);
}

com.google.Android.gms.tasks

Android向けGoogle APIタスクフレームワーク を提供します( ParseBolts と同じように)、これは-に似ています 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();
    }

}

アイデアを得たので、setBookmarksloadSampleDataも非同期であるとします。シーケンスで実行される 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) {
        // ...
    }
}
5
JP Ventura

クラスがロードされたら、シングルトンを初期化する必要があります。これをあなたのコードに入れてください:

private static BookSingleton  model = new BookSingleton();

private BookSingleton() {
}

public static BookSingleton getModel() {     return model == null ? new BookSingleton() : model;
}
0
Gilson Silva