在Firebase侦听器中设置Singleton属性值

Ant*_*aya 21 java singleton android firebase firebase-realtime-database

我目前正在测试Firebase以及我打算在整个应用程序的生命周期中使用的Singleton模型.我现在已经陷入了一些似乎非常微不足道的事情,但我无法理解我的生活.我有一个我使用的模型示例: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);
}
Run Code Online (Sandbox Code Playgroud)

现在,当我使用Singleton集的实例启动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
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

Fra*_*len 27

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);
}
Run Code Online (Sandbox Code Playgroud)

与您可能期望的相反,日志语句的顺序将是:

Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
Run Code Online (Sandbox Code Playgroud)

处理此加载的异步性质有两种选择:

  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);
}
Run Code Online (Sandbox Code Playgroud)

更新(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(); }
    });
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,我们不只是等待单个值事件,而是处理所有这些事件.这意味着无论何时更改书签,onDataChange都会执行并重新加载示例数据(或任何其他符合您应用程序需求的操作).

为了使代码更具可重用性,您可能需要定义自己的回调接口,而不是调用精确的代码onDataChange.看看这个答案就是一个很好的例子.

  • 信号量对我来说看起来非常h​​ackey所以我没有尝试实现它.但是,您的解释确实帮助我理解了Firebase的异步特性,这使我开始考虑在事件监听器中设置所有逻辑.非常感谢你:D (2认同)
  • 您可以使用CountDownLatch轻松地转换成几行同步。有时,事情不能很好地配合,因此有必要。例如,小部件不支持异步。 (2认同)

JP *_*ura 5

TL; DR:拥抱Firebase异步性

正如我在另一篇文章中提到,您可以使用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);
}
Run Code Online (Sandbox Code Playgroud)

com.google.android.gms.tasks

适用于Android的Google API提供了一个任务框架(就像Parse使用Bolts所做的一样),类似于JavaScript promises概念。

首先,您创建一个Task用于从Firebase下载书签的:

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();
    }

}
Run Code Online (Sandbox Code Playgroud)

现在您有了主意,对此进行了假设,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) {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)