Установка значения свойства Singleton в Firebase Listener
В настоящее время я тестирую Firebase вместе с моделью Singleton, которую планирую использовать для доступа во время жизненного цикла всего приложения. Я теперь застрял с чем-то, что кажется действительно тривиальным, но я не могу понять его жизнь. У меня есть образец модели, которую я использую: Закладки в firebase.
public class BookSingleton { private static BookSingleton model; private ArrayList bookmarks = new ArrayList(); public static BookSingleton getModel() { if (model == null) { throw new IllegalStateException("The model has not been initialised yet."); } return model; } public ArrayList theBookmarkList() { return this.bookmarks; } public void setBookmarks(ArrayList bookmarks){ this.bookmarks = bookmarks; } public void loadModelWithDataFromFirebase(){ Firebase db = new Firebase(//url); Firebase bookmarksRef = fb.child(//access correct child); final ArrayList loadedBookmarks = new ArrayList(); 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
- Android: как получить текущий день недели (понедельник и т. Д.) На языке пользователя?
- Android java.net.UnknownHostException: Хост не решен (вопрос страtagsи)
- Создайте библиотеку Jar для Android для распространения
- Android Studio JAR-файл, вызывающий превышение лимита GC overhead
- Облачные сообщения Google в Delphi XE5?
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 bookSamples = new ArrayList; // 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
Как я могу сделать эту работу?
- Сколько способов конвертировать bitmap в строку и наоборот?
- Как исправить android.os.NetworkOnMainThreadException?
- Получить сгенерированный идентификатор после вставки
- Реализация паузы / возобновления загрузки файла
- R.raw.any не может быть разрешено
- конкатенация строки и чисел Java
- Как Java конвертирует int в байты?
- Обслуживание статических файлов со встроенным Jetty
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 loadedBookmarks = new ArrayList (); 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
У вас есть два варианта решения асинхронного характера этой загрузки:
-
squash asynchronous баг (обычно сопровождаемый бормотание фразы типа: «это была ошибка, эти люди не знают, что они делают»)
-
обнимать асинхронного зверя (обычно сопровождаемого довольно часами проклятия, но через некоторое время мирными и лучшими заявлениями)
Возьмите синюю таблетку – сделайте синхронный 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 loadedBookmarks = new ArrayList (); 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 более тонкий, когда дело доходит до streamовой передачи. Не стесняйтесь попробовать и заставить его работать … или
Возьмите красную таблетку – поговорите с асинхронным характером синхронизации данных в Firebase
Если вы предпочитаете использовать асинхронное программирование, вы должны пересмотреть логику своего приложения.
В настоящее время у вас есть «Сначала загрузите закладки, затем загрузите данные образца, а затем загрузите еще больше».
С асинхронной моделью загрузки вы должны думать, что «всякий раз, когда закладки загружены, я хочу загрузить образцы данных. Всякий раз, когда загруженные образцы данных загружаются, я хочу загрузить еще больше».
Бонус мышления таким образом заключается в том, что он также работает, когда данные могут постоянно меняться и, таким образом, синхронизироваться несколько раз: «Всякий раз, когда изменения закладок меняются, я хочу также загрузить образцы данных. Всякий раз, когда данные образца меняются, я хочу загрузить даже Больше.”
В коде это приводит к вложенным вызовам или цепочкам событий:
public void synchronizeBookmarks(){ Firebase db = new Firebase(//url); Firebase bookmarksRef = fb.child(//access correct child); final ArrayList loadedBookmarks = new ArrayList (); 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(); } }); }
В приведенном выше коде мы не просто ждем единственного события value, но вместо этого имеем дело со всеми из них. Это означает, что всякий раз, когда закладки изменяются, выполняется onDataChange
и мы (повторно) загружаем образцы данных (или любое другое действие соответствует потребностям вашего приложения).
Обновление (20171227): Чтобы сделать код более многоразовым, вы можете определить свой собственный интерфейс обратного вызова, вместо того, чтобы называть точный код в onDataChange
. Посмотрите на этот anwer для хорошего примера.
TL; DR: Embrace Firebase Asynchronicity
Как я упоминал в другом посте , вы можете иметь дело с асинхронным характером Firebase, используя обещания. Это будет так:
public Task> synchronizeBookmarks(List bookmarks) { return Tasks.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 для Android предоставляет платформу задач (как и Parse with Bolts ), которая похожа на концепцию обещаний JavaScript .
Сначала вы создаете Task
для загрузки закладки из Firebase:
class GetBook implements Continuation> { @Override public Task then(Task task) { TaskCompletionSource 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
что setBookmarks
и loadSampleData
также асинхронны. Вы также можете создавать их как задачи Continuation
(как и предыдущие), которые будут выполняться последовательно:
class AppendBookmark(List bookmarks) implements Continuation, Task { final List bookmarks; LoadBookmarks(List bookmarks) { this.bookmark = bookmark; } @Override Task> then(Task task) { TaskCompletionSource> tcs = new TaskCompletionSource(); bookmarks.add(task.getResult()); tcs.setResult(this.bookmarks); return tcs.getTask(); } } class LoadSampleData implements Continuation, List> { @Override public Task> then(Task> task) { // ... } }
Вы должны инициализировать Singleton при загрузке classа. Поместите это на свой код:
private static BookSingleton model = new BookSingleton(); private BookSingleton() { } public static BookSingleton getModel() { return model == null ? new BookSingleton() : model; }