Подтвердить что ты не робот

Установка значения свойства Singleton в Firebase Listener

В настоящее время я тестирую 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);
}

Теперь, когда я запускаю mainActivity с экземпляром набора Singleton, я получаю нулевую ошибку, потому что ясно, что функция, которую я написал для загрузки данных модели из 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

Как я могу сделать эту работу?

4b9b3361

Ответ 1

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

У вас есть два варианта решения асинхронной природы этой загрузки:

  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. Посмотрите на этот ответ для хорошего примера этого.

Ответ 2

TL; DR: Embrace Asynchronicity Firebase

Как я упоминал в другом сообщении , вы можете иметь дело с асинхронным характером Firebase, используя promises. Это будет так:

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

API Google для Android предоставляет рамки задач (как 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();
    }

}

Теперь, когда вы получили эту идею, предположите, что 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) {
        // ...
    }
}

Ответ 3

Вы должны инициализировать Singleton при загрузке класса. Поместите это в свой код:

private static BookSingleton  model = new BookSingleton();

private BookSingleton() {
}

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