在 Firebase 监听器中设置 Singleton 属性 值
Setting Singleton property value in Firebase Listener
我目前正在测试 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);
}
现在,当我使用 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
我怎样才能完成这项工作?
加载 class 时,您必须初始化单例。
把这个放在你的代码中:
private static BookSingleton model = new BookSingleton();
private BookSingleton() {
}
public static BookSingleton getModel() { return model == null ? new BookSingleton() : model;
}
Firebase 异步加载和同步数据。所以你的 loadModelWithDataFromFirebase()
不会等待加载完成,它只是 开始 从数据库加载数据。当您的 loadModelWithDataFromFirebase()
函数 returns 时,加载尚未完成。
您可以使用一些适当放置的日志语句轻松地自行测试:
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 error) { throw error.toException(); }
});
Log.v("Async101", "Returning loaded bookmarks");
setBookmarks(loadedBookmarks);
}
与您可能期望的相反,日志语句的顺序将是:
Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
您有两种选择来处理此加载的异步性质:
消除异步错误(通常伴随着喃喃自语的短语,例如:“这是一个错误,这些人不知道他们在做什么”)
拥抱异步野兽(通常伴随着数小时的诅咒,但一段时间后和平和表现更好的应用程序)
服用蓝色药丸 - 使异步调用行为同步
如果您想选择第一个选项,放置适当的同步原语就可以了:
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 error) { throw error.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 error) { throw error.toException(); }
});
}
在上面的代码中,我们不只是等待单个值事件,而是处理所有这些事件。这意味着每当更改书签时,都会执行 onDataChange
并且我们(重新)加载示例数据(或任何其他适合您的应用程序需要的操作)。
为了让代码更加可重用,您可能希望定义自己的回调接口,而不是调用onDataChange
中的精确代码。看看 中的一个很好的例子。
TL;DR:拥抱 Firebase 异步性
正如我在另一个 post 中提到的,您可以使用 promises 处理 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
Google API for Android provides a task framework (just like Parse did with Bolts), which is similar to 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();
}
}
既然你明白了,假设 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) {
// ...
}
}
我目前正在测试 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);
}
现在,当我使用 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
我怎样才能完成这项工作?
加载 class 时,您必须初始化单例。 把这个放在你的代码中:
private static BookSingleton model = new BookSingleton();
private BookSingleton() {
}
public static BookSingleton getModel() { return model == null ? new BookSingleton() : model;
}
Firebase 异步加载和同步数据。所以你的 loadModelWithDataFromFirebase()
不会等待加载完成,它只是 开始 从数据库加载数据。当您的 loadModelWithDataFromFirebase()
函数 returns 时,加载尚未完成。
您可以使用一些适当放置的日志语句轻松地自行测试:
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 error) { throw error.toException(); }
});
Log.v("Async101", "Returning loaded bookmarks");
setBookmarks(loadedBookmarks);
}
与您可能期望的相反,日志语句的顺序将是:
Start loading bookmarks
Returning loaded bookmarks
Done loading bookmarks
您有两种选择来处理此加载的异步性质:
消除异步错误(通常伴随着喃喃自语的短语,例如:“这是一个错误,这些人不知道他们在做什么”)
拥抱异步野兽(通常伴随着数小时的诅咒,但一段时间后和平和表现更好的应用程序)
服用蓝色药丸 - 使异步调用行为同步
如果您想选择第一个选项,放置适当的同步原语就可以了:
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 error) { throw error.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 error) { throw error.toException(); }
});
}
在上面的代码中,我们不只是等待单个值事件,而是处理所有这些事件。这意味着每当更改书签时,都会执行 onDataChange
并且我们(重新)加载示例数据(或任何其他适合您的应用程序需要的操作)。
为了让代码更加可重用,您可能希望定义自己的回调接口,而不是调用onDataChange
中的精确代码。看看
TL;DR:拥抱 Firebase 异步性
正如我在另一个 post 中提到的,您可以使用 promises 处理 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
Google API for Android provides a task framework (just like Parse did with Bolts), which is similar to 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();
}
}
既然你明白了,假设 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) {
// ...
}
}