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

目前,我正在testingFirebase以及计划在整个应用生命周期中使用的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); } 

现在,当我用Singleton集的实例启动mainActivity时,我得到一个空错误,因为很明显,我写的从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,所以我不知道为什么这不工作,因为我期望它。

谢谢!

Firebase将asynchronous加载和同步数据。 所以你的loadModelWithDataFromFirebase()不会等待加载完成,它只是开始从数据库加载数据。 到loadModelWithDataFromFirebase()函数返回时,加载还没有完成。

您可以使用一些放置良好的日志语句来轻松地testing它:

 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 

你有两个select来处理这个加载的asynchronous性质:

  1. 压缩asynchronous错误(通常伴随着诸如“这是一个错误,这些人不知道他们在做什么”这样的短语)

  2. 拥抱asynchronous的野兽(通常伴随着相当的几个小时的诅咒,但一段时间后,通过和平和更好的performance应用程序)

采取蓝色药丸 – 使asynchronous调用行为同步

如果你喜欢挑选第一个选项,那么一个很好的同步原语就可以做到这一点:

 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) { } }); semaphore.acquire(); setBookmarks(loadedBookmarks); } 

更新(20160303) :当我刚刚在Android上进行testing时,它阻止了我的应用程序。 它在一个普通的JVM上运行良好,但是Android在线程方面更加挑剔。 随意尝试,使其工作…或

采取红色药丸 – 处理Firebase中数据同步的asynchronous性质

如果您select接受asynchronous编程,则应该重新考虑应用程序的逻辑。

您目前有“首先加载书签,然后加载样本数据,然后加载更多”。

使用asynchronous加载模型时,您应该这样想:“每当加载书签时,我都要加载样本数据,每当样本数据加载时,我都要加载更多。 这样思考的好处是它也可以在数据可以被改变并同步多次的时候起作用:“每当书签改变的时候,我也想加载样本数据,每当样本数据改变的时候,我想加载更多“。

在代码中,这导致嵌套的调用或事件链:

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

在上面的代码中,我们不只是等待一个值事件,而是处理所有这些事件。 这意味着,每当书签被改变, onDataChange被执行,我们(重新)加载样本数据(或任何其他行动适合您的应用程序的需要)。

TL; DR:拥抱Firebase的asynchronous性

正如我在另一篇文章中提到的 ,您可以使用promise来处理Firebase的asynchronous性质。 这将是这样的:

 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

Google API for Android提供了一个任务框架 (就像Parse与Bolts做的一样),这与JavaScript promise概念类似。

首先,您创build一个用于从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也是asynchronous的。 您也可以将它们创build为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; }